diff --git a/packages/kbn-config-schema/src/index.ts b/packages/kbn-config-schema/src/index.ts index ea155ef66c1d6b..d86d4c9dca1240 100644 --- a/packages/kbn-config-schema/src/index.ts +++ b/packages/kbn-config-schema/src/index.ts @@ -39,6 +39,7 @@ import { NumberOptions, NumberType, ObjectType, + ObjectTypeOptions, Props, RecordOfOptions, RecordOfType, @@ -94,10 +95,7 @@ function maybe(type: Type): Type { return new MaybeType(type); } -function object

( - props: P, - options?: TypeOptions<{ [K in keyof P]: TypeOf }> -): ObjectType

{ +function object

(props: P, options?: ObjectTypeOptions

): ObjectType

{ return new ObjectType(props, options); } diff --git a/packages/kbn-config-schema/src/types/__snapshots__/object_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/object_type.test.ts.snap index 5a31c3b0a7c9e8..c5e47ac09f0347 100644 --- a/packages/kbn-config-schema/src/types/__snapshots__/object_type.test.ts.snap +++ b/packages/kbn-config-schema/src/types/__snapshots__/object_type.test.ts.snap @@ -1,9 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`allowUnknowns = true affects only own keys 1`] = `"[foo.baz]: definition for this key is missing"`; + exports[`called with wrong type 1`] = `"expected a plain object value, but found [string] instead."`; exports[`called with wrong type 2`] = `"expected a plain object value, but found [number] instead."`; +exports[`does not allow unknown keys when allowUnknowns = false 1`] = `"[bar]: definition for this key is missing"`; + exports[`fails if key does not exist in schema 1`] = `"[bar]: definition for this key is missing"`; exports[`fails if missing required value 1`] = `"[name]: expected value of type [string] but got [undefined]"`; diff --git a/packages/kbn-config-schema/src/types/index.ts b/packages/kbn-config-schema/src/types/index.ts index 4dd689425a7816..727e2c65030214 100644 --- a/packages/kbn-config-schema/src/types/index.ts +++ b/packages/kbn-config-schema/src/types/index.ts @@ -28,7 +28,7 @@ export { LiteralType } from './literal_type'; export { MaybeType } from './maybe_type'; export { MapOfOptions, MapOfType } from './map_type'; export { NumberOptions, NumberType } from './number_type'; -export { ObjectType, Props, TypeOf } from './object_type'; +export { ObjectType, ObjectTypeOptions, Props, TypeOf } from './object_type'; export { RecordOfOptions, RecordOfType } from './record_type'; export { StringOptions, StringType } from './string_type'; export { UnionType } from './union_type'; diff --git a/packages/kbn-config-schema/src/types/object_type.test.ts b/packages/kbn-config-schema/src/types/object_type.test.ts index e47932a3a3ed2f..41bba1a78d4786 100644 --- a/packages/kbn-config-schema/src/types/object_type.test.ts +++ b/packages/kbn-config-schema/src/types/object_type.test.ts @@ -211,3 +211,47 @@ test('individual keys can validated', () => { `"bar is not a valid part of this schema"` ); }); + +test('allow unknown keys when allowUnknowns = true', () => { + const type = schema.object( + { foo: schema.string({ defaultValue: 'test' }) }, + { allowUnknowns: true } + ); + + expect( + type.validate({ + bar: 'baz', + }) + ).toEqual({ + foo: 'test', + bar: 'baz', + }); +}); + +test('allowUnknowns = true affects only own keys', () => { + const type = schema.object( + { foo: schema.object({ bar: schema.string() }) }, + { allowUnknowns: true } + ); + + expect(() => + type.validate({ + foo: { + bar: 'bar', + baz: 'baz', + }, + }) + ).toThrowErrorMatchingSnapshot(); +}); + +test('does not allow unknown keys when allowUnknowns = false', () => { + const type = schema.object( + { foo: schema.string({ defaultValue: 'test' }) }, + { allowUnknowns: false } + ); + expect(() => + type.validate({ + bar: 'baz', + }) + ).toThrowErrorMatchingSnapshot(); +}); diff --git a/packages/kbn-config-schema/src/types/object_type.ts b/packages/kbn-config-schema/src/types/object_type.ts index 7ee4014b08d0ef..986448481cd83f 100644 --- a/packages/kbn-config-schema/src/types/object_type.ts +++ b/packages/kbn-config-schema/src/types/object_type.ts @@ -28,25 +28,31 @@ export type TypeOf> = RT['type']; // Because of https://github.com/Microsoft/TypeScript/issues/14041 // this might not have perfect _rendering_ output, but it will be typed. - export type ObjectResultType

= Readonly<{ [K in keyof P]: TypeOf }>; +export type ObjectTypeOptions

= TypeOptions< + { [K in keyof P]: TypeOf } +> & { + allowUnknowns?: boolean; +}; + export class ObjectType

extends Type> { private props: Record; - constructor(props: P, options: TypeOptions<{ [K in keyof P]: TypeOf }> = {}) { + constructor(props: P, options: ObjectTypeOptions

= {}) { const schemaKeys = {} as Record; for (const [key, value] of Object.entries(props)) { schemaKeys[key] = value.getSchema(); } - + const { allowUnknowns, ...typeOptions } = options; const schema = internals .object() .keys(schemaKeys) .optional() - .default(); + .default() + .unknown(Boolean(allowUnknowns)); - super(schema, options); + super(schema, typeOptions); this.props = schemaKeys; }