From 98ce3d51254ab51d3cde8f4959d4cbbafd874ffc Mon Sep 17 00:00:00 2001 From: restrry Date: Mon, 1 Jul 2019 12:26:02 +0200 Subject: [PATCH 1/5] support null for literal type --- packages/kbn-config-schema/src/index.ts | 2 +- .../src/types/__snapshots__/literal_type.test.ts.snap | 2 ++ packages/kbn-config-schema/src/types/literal_type.test.ts | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/kbn-config-schema/src/index.ts b/packages/kbn-config-schema/src/index.ts index d86d4c9dca124..a5c7c9ec3276d 100644 --- a/packages/kbn-config-schema/src/index.ts +++ b/packages/kbn-config-schema/src/index.ts @@ -72,7 +72,7 @@ function uri(options?: URIOptions): Type { return new URIType(options); } -function literal(value: T): Type { +function literal(value: T): Type { return new LiteralType(value); } diff --git a/packages/kbn-config-schema/src/types/__snapshots__/literal_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/literal_type.test.ts.snap index 179e3e4251423..14d474b4a516b 100644 --- a/packages/kbn-config-schema/src/types/__snapshots__/literal_type.test.ts.snap +++ b/packages/kbn-config-schema/src/types/__snapshots__/literal_type.test.ts.snap @@ -9,3 +9,5 @@ exports[`returns error when not correct 2`] = `"expected value to equal [true] b exports[`returns error when not correct 3`] = `"expected value to equal [test] but got [1,2,3]"`; exports[`returns error when not correct 4`] = `"expected value to equal [123] but got [abc]"`; + +exports[`returns error when not correct 5`] = `"expected value to equal [null] but got [42]"`; diff --git a/packages/kbn-config-schema/src/types/literal_type.test.ts b/packages/kbn-config-schema/src/types/literal_type.test.ts index 5ee0ac4edff68..a5ddff3152368 100644 --- a/packages/kbn-config-schema/src/types/literal_type.test.ts +++ b/packages/kbn-config-schema/src/types/literal_type.test.ts @@ -33,6 +33,10 @@ test('handles number', () => { expect(literal(123).validate(123)).toBe(123); }); +test('handles null', () => { + expect(literal(null).validate(null)).toBe(null); +}); + test('returns error when not correct', () => { expect(() => literal('test').validate('foo')).toThrowErrorMatchingSnapshot(); @@ -41,6 +45,8 @@ test('returns error when not correct', () => { expect(() => literal('test').validate([1, 2, 3])).toThrowErrorMatchingSnapshot(); expect(() => literal(123).validate('abc')).toThrowErrorMatchingSnapshot(); + + expect(() => literal(null).validate(42)).toThrowErrorMatchingSnapshot(); }); test('includes namespace in failure', () => { From b437a9c690f38eea807c39febc3abb10653ac895 Mon Sep 17 00:00:00 2001 From: restrry Date: Mon, 1 Jul 2019 13:25:19 +0200 Subject: [PATCH 2/5] support schemas withing right operand of conditional type --- packages/kbn-config-schema/src/index.ts | 2 +- .../src/references/reference.ts | 2 +- .../conditional_type.test.ts.snap | 8 +++ .../src/types/conditional_type.test.ts | 66 +++++++++++++++++++ .../src/types/conditional_type.ts | 9 ++- 5 files changed, 82 insertions(+), 5 deletions(-) diff --git a/packages/kbn-config-schema/src/index.ts b/packages/kbn-config-schema/src/index.ts index a5c7c9ec3276d..f6f83bfd74664 100644 --- a/packages/kbn-config-schema/src/index.ts +++ b/packages/kbn-config-schema/src/index.ts @@ -167,7 +167,7 @@ function siblingRef(key: string): SiblingReference { function conditional( leftOperand: Reference, - rightOperand: Reference | A, + rightOperand: Reference | A | Type, equalType: Type, notEqualType: Type, options?: TypeOptions diff --git a/packages/kbn-config-schema/src/references/reference.ts b/packages/kbn-config-schema/src/references/reference.ts index 5dffc990f3b7b..9af1f910053ae 100644 --- a/packages/kbn-config-schema/src/references/reference.ts +++ b/packages/kbn-config-schema/src/references/reference.ts @@ -22,7 +22,7 @@ import { internals, Reference as InternalReference } from '../internals'; export class Reference { public static isReference(value: V | Reference | undefined): value is Reference { return ( - value !== undefined && + value != null && typeof (value as Reference).getSchema === 'function' && internals.isRef((value as Reference).getSchema()) ); diff --git a/packages/kbn-config-schema/src/types/__snapshots__/conditional_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/conditional_type.test.ts.snap index 2e0ada23eb5fd..b32db114860f5 100644 --- a/packages/kbn-config-schema/src/types/__snapshots__/conditional_type.test.ts.snap +++ b/packages/kbn-config-schema/src/types/__snapshots__/conditional_type.test.ts.snap @@ -18,6 +18,14 @@ exports[`properly validates types according chosen schema 1`] = `"value is [a] b exports[`properly validates types according chosen schema 2`] = `"value is [ab] but it must have a maximum length of [1]."`; +exports[`properly validates when compares with "null" literal Schema 1`] = `"value is [a] but it must have a minimum length of [2]."`; + +exports[`properly validates when compares with "null" literal Schema 2`] = `"value is [ab] but it must have a minimum length of [3]."`; + +exports[`properly validates when compares with Schema 1`] = `"value is [a] but it must have a minimum length of [2]."`; + +exports[`properly validates when compares with Schema 2`] = `"value is [ab] but it must have a minimum length of [3]."`; + exports[`required by default 1`] = `"expected value of type [string] but got [undefined]"`; exports[`works with both context and sibling references 1`] = `"[value]: expected value of type [string] but got [number]"`; diff --git a/packages/kbn-config-schema/src/types/conditional_type.test.ts b/packages/kbn-config-schema/src/types/conditional_type.test.ts index a72c3463e00cb..354854b864755 100644 --- a/packages/kbn-config-schema/src/types/conditional_type.test.ts +++ b/packages/kbn-config-schema/src/types/conditional_type.test.ts @@ -114,6 +114,72 @@ test('properly validates types according chosen schema', () => { ).toEqual('a'); }); +test('properly validates when compares with Schema', () => { + const type = schema.conditional( + schema.contextRef('context_value_1'), + schema.number(), + schema.string({ minLength: 2 }), + schema.string({ minLength: 3 }) + ); + + expect(() => + type.validate('a', { + context_value_1: 0, + }) + ).toThrowErrorMatchingSnapshot(); + + expect( + type.validate('ab', { + context_value_1: 0, + }) + ).toEqual('ab'); + + expect(() => + type.validate('ab', { + context_value_1: 'b', + }) + ).toThrowErrorMatchingSnapshot(); + + expect( + type.validate('abc', { + context_value_1: 'b', + }) + ).toEqual('abc'); +}); + +test('properly validates when compares with "null" literal Schema', () => { + const type = schema.conditional( + schema.contextRef('context_value_1'), + schema.literal(null), + schema.string({ minLength: 2 }), + schema.string({ minLength: 3 }) + ); + + expect(() => + type.validate('a', { + context_value_1: null, + }) + ).toThrowErrorMatchingSnapshot(); + + expect( + type.validate('ab', { + context_value_1: null, + }) + ).toEqual('ab'); + + expect(() => + type.validate('ab', { + context_value_1: 'b', + }) + ).toThrowErrorMatchingSnapshot(); + + expect( + type.validate('abc', { + context_value_1: 'b', + }) + ).toEqual('abc'); +}); + test('properly handles schemas with incompatible types', () => { const type = schema.conditional( schema.contextRef('context_value_1'), diff --git a/packages/kbn-config-schema/src/types/conditional_type.ts b/packages/kbn-config-schema/src/types/conditional_type.ts index acba5fa2cedfb..fb744082874b2 100644 --- a/packages/kbn-config-schema/src/types/conditional_type.ts +++ b/packages/kbn-config-schema/src/types/conditional_type.ts @@ -27,15 +27,18 @@ export type ConditionalTypeValue = string | number | boolean | object | null; export class ConditionalType extends Type { constructor( leftOperand: Reference, - rightOperand: Reference | A, + rightOperand: Reference | A | Type, equalType: Type, notEqualType: Type, options?: TypeOptions ) { const schema = internals.when(leftOperand.getSchema(), { - is: Reference.isReference(rightOperand) ? rightOperand.getSchema() : rightOperand, - otherwise: notEqualType.getSchema(), + is: + Reference.isReference(rightOperand) || rightOperand instanceof Type + ? rightOperand.getSchema() + : rightOperand, then: equalType.getSchema(), + otherwise: notEqualType.getSchema(), }); super(schema, options); From 24218f7d50324b31c55b16be8cfb562c6b937988 Mon Sep 17 00:00:00 2001 From: restrry Date: Mon, 1 Jul 2019 15:14:06 +0200 Subject: [PATCH 3/5] add never type to ban usage --- packages/kbn-config-schema/src/index.ts | 7 ++ .../__snapshots__/never_type.test.ts.snap | 15 ++++ packages/kbn-config-schema/src/types/index.ts | 1 + .../src/types/never_type.test.ts | 81 +++++++++++++++++++ .../kbn-config-schema/src/types/never_type.ts | 41 ++++++++++ 5 files changed, 145 insertions(+) create mode 100644 packages/kbn-config-schema/src/types/__snapshots__/never_type.test.ts.snap create mode 100644 packages/kbn-config-schema/src/types/never_type.test.ts create mode 100644 packages/kbn-config-schema/src/types/never_type.ts diff --git a/packages/kbn-config-schema/src/index.ts b/packages/kbn-config-schema/src/index.ts index f6f83bfd74664..bda1c36f8fe50 100644 --- a/packages/kbn-config-schema/src/index.ts +++ b/packages/kbn-config-schema/src/index.ts @@ -36,6 +36,8 @@ import { MapOfOptions, MapOfType, MaybeType, + NeverOptions, + NeverType, NumberOptions, NumberType, ObjectType, @@ -88,6 +90,10 @@ function duration(options?: DurationOptions): Type { return new DurationType(options); } +function never(options?: NeverOptions): Type { + return new NeverType(options); +} + /** * Create an optional type */ @@ -186,6 +192,7 @@ export const schema = { literal, mapOf, maybe, + never, number, object, oneOf, diff --git a/packages/kbn-config-schema/src/types/__snapshots__/never_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/never_type.test.ts.snap new file mode 100644 index 0000000000000..69961b633afa3 --- /dev/null +++ b/packages/kbn-config-schema/src/types/__snapshots__/never_type.test.ts.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`allows customizing error message 1`] = `"a rule was deprecated and removed, use [a] instead"`; + +exports[`throws on any value set 1`] = `"a value wasn't expected to be present"`; + +exports[`throws on any value set 2`] = `"a value wasn't expected to be present"`; + +exports[`throws on any value set 3`] = `"a value wasn't expected to be present"`; + +exports[`throws on any value set 4`] = `"a value wasn't expected to be present"`; + +exports[`throws on value set as object property 1`] = `"[name]: a value wasn't expected to be present"`; + +exports[`works for conditional types 1`] = `"[name]: a value wasn't expected to be present"`; diff --git a/packages/kbn-config-schema/src/types/index.ts b/packages/kbn-config-schema/src/types/index.ts index 727e2c6503021..c1ec556ebec69 100644 --- a/packages/kbn-config-schema/src/types/index.ts +++ b/packages/kbn-config-schema/src/types/index.ts @@ -33,3 +33,4 @@ export { RecordOfOptions, RecordOfType } from './record_type'; export { StringOptions, StringType } from './string_type'; export { UnionType } from './union_type'; export { URIOptions, URIType } from './uri_type'; +export { NeverOptions, NeverType } from './never_type'; diff --git a/packages/kbn-config-schema/src/types/never_type.test.ts b/packages/kbn-config-schema/src/types/never_type.test.ts new file mode 100644 index 0000000000000..52e4a68317f2c --- /dev/null +++ b/packages/kbn-config-schema/src/types/never_type.test.ts @@ -0,0 +1,81 @@ +/* + * 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('throws on any value set', () => { + const type = schema.never(); + + expect(() => type.validate(1)).toThrowErrorMatchingSnapshot(); + expect(() => type.validate('a')).toThrowErrorMatchingSnapshot(); + expect(() => type.validate(null)).toThrowErrorMatchingSnapshot(); + expect(() => type.validate({})).toThrowErrorMatchingSnapshot(); + expect(() => type.validate(undefined)).not.toThrow(); +}); + +test('throws on value set as object property', () => { + const type = schema.object({ + name: schema.never(), + status: schema.string(), + }); + + expect(() => + type.validate({ name: 'name', status: 'in progress' }) + ).toThrowErrorMatchingSnapshot(); + + expect(() => type.validate({ status: 'in progress' })).not.toThrow(); + expect(() => type.validate({ name: undefined, status: 'in progress' })).not.toThrow(); +}); + +test('works for conditional types', () => { + const type = schema.object({ + name: schema.conditional( + schema.contextRef('context_value_1'), + schema.contextRef('context_value_2'), + schema.string(), + schema.never() + ), + }); + + expect( + type.validate( + { name: 'a' }, + { + context_value_1: 0, + context_value_2: 0, + } + ) + ).toEqual({ name: 'a' }); + + expect(() => + type.validate( + { name: 'a' }, + { + context_value_1: 0, + context_value_2: 1, + } + ) + ).toThrowErrorMatchingSnapshot(); +}); + +test('allows customizing error message', () => { + const type = schema.never({ message: 'a rule was deprecated and removed, use [a] instead' }); + + expect(() => type.validate(1)).toThrowErrorMatchingSnapshot(); +}); diff --git a/packages/kbn-config-schema/src/types/never_type.ts b/packages/kbn-config-schema/src/types/never_type.ts new file mode 100644 index 0000000000000..74cf056dc2d60 --- /dev/null +++ b/packages/kbn-config-schema/src/types/never_type.ts @@ -0,0 +1,41 @@ +/* + * 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 { internals } from '../internals'; +import { Type, TypeOptions } from './type'; + +export type NeverOptions = TypeOptions & { + message?: string; +}; + +export class NeverType extends Type { + private readonly message?: string; + constructor(options: NeverOptions = {}) { + const { message, ...typeOptions } = options; + super(internals.any().forbidden(), typeOptions); + this.message = message; + } + + protected handleError(type: string) { + switch (type) { + case 'any.unknown': + return this.message ? this.message : "a value wasn't expected to be present"; + } + } +} From 784e096d8b5dd3645d2bef8d4541fd0fa42d5706 Mon Sep 17 00:00:00 2001 From: restrry Date: Wed, 3 Jul 2019 09:15:55 +0200 Subject: [PATCH 4/5] remove never options. add when needed --- packages/kbn-config-schema/src/index.ts | 5 ++--- .../types/__snapshots__/never_type.test.ts.snap | 2 -- packages/kbn-config-schema/src/types/index.ts | 2 +- .../src/types/never_type.test.ts | 6 ------ .../kbn-config-schema/src/types/never_type.ts | 15 ++++----------- 5 files changed, 7 insertions(+), 23 deletions(-) diff --git a/packages/kbn-config-schema/src/index.ts b/packages/kbn-config-schema/src/index.ts index bda1c36f8fe50..b4308dc3f80d1 100644 --- a/packages/kbn-config-schema/src/index.ts +++ b/packages/kbn-config-schema/src/index.ts @@ -36,7 +36,6 @@ import { MapOfOptions, MapOfType, MaybeType, - NeverOptions, NeverType, NumberOptions, NumberType, @@ -90,8 +89,8 @@ function duration(options?: DurationOptions): Type { return new DurationType(options); } -function never(options?: NeverOptions): Type { - return new NeverType(options); +function never(): Type { + return new NeverType(); } /** diff --git a/packages/kbn-config-schema/src/types/__snapshots__/never_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/never_type.test.ts.snap index 69961b633afa3..6eea2a7cefc72 100644 --- a/packages/kbn-config-schema/src/types/__snapshots__/never_type.test.ts.snap +++ b/packages/kbn-config-schema/src/types/__snapshots__/never_type.test.ts.snap @@ -1,7 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`allows customizing error message 1`] = `"a rule was deprecated and removed, use [a] instead"`; - exports[`throws on any value set 1`] = `"a value wasn't expected to be present"`; exports[`throws on any value set 2`] = `"a value wasn't expected to be present"`; diff --git a/packages/kbn-config-schema/src/types/index.ts b/packages/kbn-config-schema/src/types/index.ts index c1ec556ebec69..cfa8cc4b7553d 100644 --- a/packages/kbn-config-schema/src/types/index.ts +++ b/packages/kbn-config-schema/src/types/index.ts @@ -33,4 +33,4 @@ export { RecordOfOptions, RecordOfType } from './record_type'; export { StringOptions, StringType } from './string_type'; export { UnionType } from './union_type'; export { URIOptions, URIType } from './uri_type'; -export { NeverOptions, NeverType } from './never_type'; +export { NeverType } from './never_type'; diff --git a/packages/kbn-config-schema/src/types/never_type.test.ts b/packages/kbn-config-schema/src/types/never_type.test.ts index 52e4a68317f2c..46f0b47f56ad6 100644 --- a/packages/kbn-config-schema/src/types/never_type.test.ts +++ b/packages/kbn-config-schema/src/types/never_type.test.ts @@ -73,9 +73,3 @@ test('works for conditional types', () => { ) ).toThrowErrorMatchingSnapshot(); }); - -test('allows customizing error message', () => { - const type = schema.never({ message: 'a rule was deprecated and removed, use [a] instead' }); - - expect(() => type.validate(1)).toThrowErrorMatchingSnapshot(); -}); diff --git a/packages/kbn-config-schema/src/types/never_type.ts b/packages/kbn-config-schema/src/types/never_type.ts index 74cf056dc2d60..bb175caaaa6ac 100644 --- a/packages/kbn-config-schema/src/types/never_type.ts +++ b/packages/kbn-config-schema/src/types/never_type.ts @@ -18,24 +18,17 @@ */ import { internals } from '../internals'; -import { Type, TypeOptions } from './type'; - -export type NeverOptions = TypeOptions & { - message?: string; -}; +import { Type } from './type'; export class NeverType extends Type { - private readonly message?: string; - constructor(options: NeverOptions = {}) { - const { message, ...typeOptions } = options; - super(internals.any().forbidden(), typeOptions); - this.message = message; + constructor() { + super(internals.any().forbidden()); } protected handleError(type: string) { switch (type) { case 'any.unknown': - return this.message ? this.message : "a value wasn't expected to be present"; + return "a value wasn't expected to be present"; } } } From 1c00f346a87f7eb0922e8b6a48c34c8bbdc8a4f6 Mon Sep 17 00:00:00 2001 From: restrry Date: Wed, 3 Jul 2019 09:38:10 +0200 Subject: [PATCH 5/5] add tests for Reference.isReference --- .../src/references/reference.test.ts | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 packages/kbn-config-schema/src/references/reference.test.ts diff --git a/packages/kbn-config-schema/src/references/reference.test.ts b/packages/kbn-config-schema/src/references/reference.test.ts new file mode 100644 index 0000000000000..e87dfd52f8864 --- /dev/null +++ b/packages/kbn-config-schema/src/references/reference.test.ts @@ -0,0 +1,61 @@ +/* + * 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 { Reference } from './reference'; +import { schema } from '../'; + +describe('Reference.isReference', () => { + it('handles primitives', () => { + expect(Reference.isReference(undefined)).toBe(false); + expect(Reference.isReference(null)).toBe(false); + expect(Reference.isReference(true)).toBe(false); + expect(Reference.isReference(1)).toBe(false); + expect(Reference.isReference('a')).toBe(false); + expect(Reference.isReference({})).toBe(false); + }); + + it('handles schemas', () => { + expect( + Reference.isReference( + schema.string({ + defaultValue: 'value', + }) + ) + ).toBe(false); + + expect( + Reference.isReference( + schema.conditional( + schema.contextRef('context_value_1'), + schema.contextRef('context_value_2'), + schema.string(), + schema.string() + ) + ) + ).toBe(false); + }); + + it('handles context references', () => { + expect(Reference.isReference(schema.contextRef('ref_1'))).toBe(true); + }); + + it('handles sibling references', () => { + expect(Reference.isReference(schema.siblingRef('ref_1'))).toBe(true); + }); +});