Skip to content

Commit

Permalink
kbn/config-schema: Consider maybe properties as optional keys in Obje…
Browse files Browse the repository at this point in the history
…ctType (#63838)

* consider optional properties as optional keys in ObjectType

* fix type on security config

* fix ObjectTypeOptions
  • Loading branch information
pgayvallet authored Apr 21, 2020
1 parent f76669b commit f6f610d
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 9 deletions.
24 changes: 24 additions & 0 deletions packages/kbn-config-schema/src/types/object_type.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

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

test('returns value by default', () => {
const type = schema.object({
Expand Down Expand Up @@ -350,3 +351,26 @@ test('unknowns = `ignore` affects only own keys', () => {
})
).toThrowErrorMatchingInlineSnapshot(`"[foo.baz]: definition for this key is missing"`);
});

test('handles optional properties', () => {
const type = schema.object({
required: schema.string(),
optional: schema.maybe(schema.string()),
});

type SchemaType = TypeOf<typeof type>;

let foo: SchemaType = {
required: 'foo',
};
foo = {
required: 'hello',
optional: undefined,
};
foo = {
required: 'hello',
optional: 'bar',
};

expect(foo).toBeDefined();
});
23 changes: 19 additions & 4 deletions packages/kbn-config-schema/src/types/object_type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,26 @@ export type Props = Record<string, Type<any>>;

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

type OptionalProperties<Base extends Props> = Pick<
Base,
{
[Key in keyof Base]: undefined extends TypeOf<Base[Key]> ? Key : never;
}[keyof Base]
>;

type RequiredProperties<Base extends Props> = Pick<
Base,
{
[Key in keyof Base]: undefined extends TypeOf<Base[Key]> ? never : Key;
}[keyof Base]
>;

// Because of https://github.com/Microsoft/TypeScript/issues/14041
// this might not have perfect _rendering_ output, but it will be typed.
export type ObjectResultType<P extends Props> = Readonly<{ [K in keyof P]: TypeOf<P[K]> }>;
export type ObjectResultType<P extends Props> = Readonly<
{ [K in keyof OptionalProperties<P>]?: TypeOf<P[K]> } &
{ [K in keyof RequiredProperties<P>]: TypeOf<P[K]> }
>;

interface UnknownOptions {
/**
Expand All @@ -40,9 +57,7 @@ interface UnknownOptions {
unknowns?: 'allow' | 'ignore' | 'forbid';
}

export type ObjectTypeOptions<P extends Props = any> = TypeOptions<
{ [K in keyof P]: TypeOf<P[K]> }
> &
export type ObjectTypeOptions<P extends Props = any> = TypeOptions<ObjectResultType<P>> &
UnknownOptions;

export class ObjectType<P extends Props = any> extends Type<ObjectResultType<P>> {
Expand Down
17 changes: 12 additions & 5 deletions src/core/server/http/router/validator/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@
* under the License.
*/

import { ValidationError, Type, schema, ObjectType, isConfigSchema } from '@kbn/config-schema';
import {
ValidationError,
Type,
schema,
ObjectType,
TypeOf,
isConfigSchema,
} from '@kbn/config-schema';
import { Stream } from 'stream';
import { RouteValidationError } from './validator_error';

Expand Down Expand Up @@ -85,7 +92,7 @@ type RouteValidationResultType<T extends RouteValidationSpec<any> | undefined> =
T extends RouteValidationFunction<any>
? ReturnType<T>['value']
: T extends Type<any>
? ReturnType<T['validate']>
? TypeOf<T>
: undefined
>;

Expand Down Expand Up @@ -170,23 +177,23 @@ export class RouteValidator<P = {}, Q = {}, B = {}> {
* @internal
*/
public getParams(data: unknown, namespace?: string): Readonly<P> {
return this.validate(this.config.params, this.options.unsafe?.params, data, namespace);
return this.validate(this.config.params, this.options.unsafe?.params, data, namespace) as P;
}

/**
* Get validated query params
* @internal
*/
public getQuery(data: unknown, namespace?: string): Readonly<Q> {
return this.validate(this.config.query, this.options.unsafe?.query, data, namespace);
return this.validate(this.config.query, this.options.unsafe?.query, data, namespace) as Q;
}

/**
* Get validated body
* @internal
*/
public getBody(data: unknown, namespace?: string): Readonly<B> {
return this.validate(this.config.body, this.options.unsafe?.body, data, namespace);
return this.validate(this.config.body, this.options.unsafe?.body, data, namespace) as B;
}

/**
Expand Down

0 comments on commit f6f610d

Please sign in to comment.