Skip to content

Commit

Permalink
Introduce schema.any (#21775)
Browse files Browse the repository at this point in the history
  • Loading branch information
azasypkin authored Aug 9, 2018
1 parent 474714a commit 4cd9699
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 17 deletions.
4 changes: 2 additions & 2 deletions src/core/server/config/__tests__/config_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

/* tslint:disable max-classes-per-file */
import { BehaviorSubject, first, k$, toPromise } from '../../../lib/kbn_observable';
import { AnyType, schema, TypeOf } from '../schema';
import { schema, Type, TypeOf } from '../schema';

import { ConfigService, ObjectToRawConfigAdapter } from '..';
import { logger } from '../../logging/__mocks__';
Expand Down Expand Up @@ -268,7 +268,7 @@ test('treats config as enabled if config path is not present in config', async (
expect(unusedPaths).toEqual([]);
});

function createClassWithSchema(s: AnyType) {
function createClassWithSchema(s: Type<any>) {
return class ExampleClassWithSchema {
public static schema = s;

Expand Down
8 changes: 4 additions & 4 deletions src/core/server/config/config_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { Logger, LoggerFactory } from '../logging';
import { ConfigWithSchema } from './config_with_schema';
import { Env } from './env';
import { RawConfig } from './raw_config';
import { AnyType } from './schema';
import { Type } from './schema';

export type ConfigPath = string | string[];

Expand Down Expand Up @@ -61,7 +61,7 @@ export class ConfigService {
* @param ConfigClass A class (not an instance of a class) that contains a
* static `schema` that we validate the config at the given `path` against.
*/
public atPath<Schema extends AnyType, Config>(
public atPath<Schema extends Type<any>, Config>(
path: ConfigPath,
ConfigClass: ConfigWithSchema<Schema, Config>
) {
Expand All @@ -76,7 +76,7 @@ export class ConfigService {
*
* @see atPath
*/
public optionalAtPath<Schema extends AnyType, Config>(
public optionalAtPath<Schema extends Type<any>, Config>(
path: ConfigPath,
ConfigClass: ConfigWithSchema<Schema, Config>
) {
Expand Down Expand Up @@ -120,7 +120,7 @@ export class ConfigService {
return config.getFlattenedPaths().filter(path => !isPathHandled(path, handledPaths));
}

private createConfig<Schema extends AnyType, Config>(
private createConfig<Schema extends Type<any>, Config>(
path: ConfigPath,
rawConfig: {},
ConfigClass: ConfigWithSchema<Schema, Config>
Expand Down
4 changes: 2 additions & 2 deletions src/core/server/config/config_with_schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

// TODO inline all of these
import { Env } from './env';
import { AnyType, TypeOf } from './schema';
import { Type, TypeOf } from './schema';

/**
* Interface that defines the static side of a config class.
Expand All @@ -31,7 +31,7 @@ import { AnyType, TypeOf } from './schema';
* in TypeScript, but it can be used to ensure we have a config class that
* matches whenever it's used.
*/
export interface ConfigWithSchema<S extends AnyType, Config> {
export interface ConfigWithSchema<S extends Type<any>, Config> {
/**
* Any config class must define a schema that validates the config, based on
* the injected `schema` helper.
Expand Down
9 changes: 7 additions & 2 deletions src/core/server/config/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,13 @@ import {
UnionType,
} from './types';

export { AnyType, ObjectType, TypeOf };
export { ObjectType, TypeOf, Type };
export { ByteSizeValue } from './byte_size_value';

function any(options?: TypeOptions<any>) {
return new AnyType(options);
}

function boolean(options?: TypeOptions<boolean>): Type<boolean> {
return new BooleanType(options);
}
Expand Down Expand Up @@ -135,7 +139,7 @@ function oneOf<A, B, C>(
): Type<A | B | C>;
function oneOf<A, B>(types: [Type<A>, Type<B>], options?: TypeOptions<A | B>): Type<A | B>;
function oneOf<A>(types: [Type<A>], options?: TypeOptions<A>): Type<A>;
function oneOf<RTS extends AnyType[]>(types: RTS, options?: TypeOptions<any>): Type<any> {
function oneOf<RTS extends Array<Type<any>>>(types: RTS, options?: TypeOptions<any>): Type<any> {
return new UnionType(types, options);
}

Expand All @@ -158,6 +162,7 @@ function conditional<A extends ConditionalTypeValue, B, C>(
}

export const schema = {
any,
arrayOf,
boolean,
byteSize,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`includes namespace in failure 1`] = `"[foo-namespace]: expected value of type [any] but got [undefined]"`;

exports[`is required by default 1`] = `"expected value of type [any] but got [undefined]"`;
60 changes: 60 additions & 0 deletions src/core/server/config/schema/types/__tests__/any_type.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { schema } from '../..';

test('works for any value', () => {
expect(schema.any().validate(true)).toBe(true);
expect(schema.any().validate(100)).toBe(100);
expect(schema.any().validate('foo')).toBe('foo');
expect(schema.any().validate(null)).toBe(null);
expect(schema.any().validate({ foo: 'bar', baz: 2 })).toEqual({ foo: 'bar', baz: 2 });
});

test('is required by default', () => {
expect(() => schema.any().validate(undefined)).toThrowErrorMatchingSnapshot();
});

test('includes namespace in failure', () => {
expect(() =>
schema.any().validate(undefined, {}, 'foo-namespace')
).toThrowErrorMatchingSnapshot();
});

describe('#defaultValue', () => {
test('returns default when undefined', () => {
expect(schema.any({ defaultValue: true }).validate(undefined)).toBe(true);
expect(schema.any({ defaultValue: 200 }).validate(undefined)).toBe(200);
expect(schema.any({ defaultValue: 'bar' }).validate(undefined)).toBe('bar');
expect(schema.any({ defaultValue: { baz: 'foo' } }).validate(undefined)).toEqual({
baz: 'foo',
});
});

test('returns value when specified', () => {
expect(schema.any({ defaultValue: true }).validate(false)).toBe(false);
expect(schema.any({ defaultValue: 200 }).validate(100)).toBe(100);
expect(schema.any({ defaultValue: 'bar' }).validate('foo')).toBe('foo');
expect(schema.any({ defaultValue: 'not-null' }).validate(null)).toBe(null);
expect(schema.any({ defaultValue: { baz: 'foo' } }).validate({ foo: 'bar', baz: 2 })).toEqual({
foo: 'bar',
baz: 2,
});
});
});
16 changes: 14 additions & 2 deletions src/core/server/config/schema/types/any_type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
* under the License.
*/

import { Type } from './type';
import typeDetect from 'type-detect';
import { internals } from '../internals';
import { Type, TypeOptions } from './type';

export type AnyType = Type<any>;
export class AnyType extends Type<any> {
constructor(options?: TypeOptions<any>) {
super(internals.any(), options);
}

protected handleError(type: string, { value }: Record<string, any>) {
if (type === 'any.required') {
return `expected value of type [any] but got [${typeDetect(value)}]`;
}
}
}
5 changes: 2 additions & 3 deletions src/core/server/config/schema/types/object_type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@

import typeDetect from 'type-detect';
import { AnySchema, internals } from '../internals';
import { AnyType } from './any_type';
import { Type, TypeOptions } from './type';

export type Props = Record<string, AnyType>;
export type Props = Record<string, Type<any>>;

export type TypeOf<RT extends AnyType> = RT['type'];
export type TypeOf<RT extends Type<any>> = RT['type'];

// Because of https://github.com/Microsoft/TypeScript/issues/14041
// this might not have perfect _rendering_ output, but it will be typed.
Expand Down
3 changes: 1 addition & 2 deletions src/core/server/config/schema/types/union_type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@
import typeDetect from 'type-detect';
import { SchemaTypeError, SchemaTypesError } from '../errors';
import { internals } from '../internals';
import { AnyType } from './any_type';
import { Type, TypeOptions } from './type';

export class UnionType<RTS extends AnyType[], T> extends Type<T> {
export class UnionType<RTS extends Array<Type<any>>, T> extends Type<T> {
constructor(types: RTS, options?: TypeOptions<T>) {
const schema = internals.alternatives(types.map(type => type.getSchema()));

Expand Down

0 comments on commit 4cd9699

Please sign in to comment.