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 64739d7a4c4daa..41025046e282d0 100644 --- a/packages/kbn-config-schema/src/types/object_type.test.ts +++ b/packages/kbn-config-schema/src/types/object_type.test.ts @@ -319,3 +319,46 @@ test('does not allow unknown keys when allowUnknowns = false', () => { }) ).toThrowErrorMatchingInlineSnapshot(`"[bar]: definition for this key is missing"`); }); + +test('allow and remove unknown keys when ignoreUnknowns = true', () => { + const type = schema.object( + { foo: schema.string({ defaultValue: 'test' }) }, + { ignoreUnknowns: true } + ); + + expect( + type.validate({ + bar: 'baz', + }) + ).toEqual({ + foo: 'test', + }); +}); + +test('ignoreUnknowns = true affects only own keys', () => { + const type = schema.object( + { foo: schema.object({ bar: schema.string() }) }, + { ignoreUnknowns: true } + ); + + expect(() => + type.validate({ + foo: { + bar: 'bar', + baz: 'baz', + }, + }) + ).toThrowErrorMatchingInlineSnapshot(`"[foo.baz]: definition for this key is missing"`); +}); + +test('does not allow unknown keys when ignoreUnknowns = false', () => { + const type = schema.object( + { foo: schema.string({ defaultValue: 'test' }) }, + { ignoreUnknowns: false } + ); + expect(() => + type.validate({ + bar: 'baz', + }) + ).toThrowErrorMatchingInlineSnapshot(`"[bar]: definition for this key is missing"`); +}); diff --git a/packages/kbn-config-schema/src/types/object_type.ts b/packages/kbn-config-schema/src/types/object_type.ts index 4f3d68a6bac97d..9568edcf39dfdf 100644 --- a/packages/kbn-config-schema/src/types/object_type.ts +++ b/packages/kbn-config-schema/src/types/object_type.ts @@ -30,17 +30,35 @@ export type TypeOf> = RT['type']; // this might not have perfect _rendering_ output, but it will be typed. export type ObjectResultType

= Readonly<{ [K in keyof P]: TypeOf }>; +interface AllowUnknowns { + allowUnknowns: true; + ignoreUnknowns?: false; +} + +interface IgnoreUnknowns { + allowUnknowns?: false; + ignoreUnknowns: true; +} + +interface ForbidUnknowns { + allowUnknowns?: false; + ignoreUnknowns?: false; +} +// type check to not permit both to be set to `true` at the same time +type UnknownOptions = AllowUnknowns | IgnoreUnknowns | ForbidUnknowns; + export type ObjectTypeOptions

= TypeOptions< { [K in keyof P]: TypeOf } -> & { - /** Should uknown keys not be defined in the schema be allowed. Defaults to `false` */ - allowUnknowns?: boolean; -}; +> & + UnknownOptions; export class ObjectType

extends Type> { private props: Record; - constructor(props: P, { allowUnknowns = false, ...typeOptions }: ObjectTypeOptions

= {}) { + constructor( + props: P, + { allowUnknowns = false, ignoreUnknowns = false, ...typeOptions }: ObjectTypeOptions

= {} + ) { const schemaKeys = {} as Record; for (const [key, value] of Object.entries(props)) { schemaKeys[key] = value.getSchema(); @@ -50,7 +68,8 @@ export class ObjectType

extends Type> .keys(schemaKeys) .default() .optional() - .unknown(Boolean(allowUnknowns)); + .unknown(Boolean(allowUnknowns)) + .options({ stripUnknown: { objects: ignoreUnknowns } }); super(schema, typeOptions); this.props = schemaKeys;