From e48ca0dcda410eb5832ff072d5f61bdf4e2c447e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Unneb=C3=A4ck?= Date: Fri, 10 Aug 2018 13:20:34 +0100 Subject: [PATCH] Add support for "oneOf" to TypeScript typings --- index.d.ts | 34 +++++++++++---- test/typings.ts | 109 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 7 deletions(-) diff --git a/index.d.ts b/index.d.ts index 4df0819..44af962 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,4 @@ -type AnySchema = NullSchema | BooleanSchema | NumberSchema | StringSchema | AnyEnumSchema | AnyArraySchema | AnyObjectSchema | AnyAllOptionalObjectSchema +type AnySchema = NullSchema | BooleanSchema | NumberSchema | StringSchema | AnyEnumSchema | AnyArraySchema | AnyObjectSchema | AnyAllOptionalObjectSchema | AnyOneOfSchema type StringKeys = (keyof T) & string interface NullSchema { @@ -43,6 +43,8 @@ interface AllOptionalObjectSchema> properties: Properties } +interface AnyOneOfSchema { oneOf: AnySchema[] } + interface ArrayFromSchema extends Array> {} type ObjectFromSchema, Required extends StringKeys> = { @@ -76,16 +78,34 @@ interface Validator> { toJSON(): Schema } -interface Filter { +interface Filter> { (input: Output, options?: any): Output } interface Factory { - , Required extends StringKeys> (schema: ObjectSchema, options?: any): Validator> - (schema: Schema, options?: any): Validator - - createFilter, Required extends StringKeys> (schema: ObjectSchema, options?: any): Filter> - createFilter (schema: Schema, options?: any): Filter> + /* One of object schema */ + , Required1 extends StringKeys, Properties2 extends Record, Required2 extends StringKeys> (schema: { oneOf: [ObjectSchema, ObjectSchema] }, options?: any): Validator<{ oneOf: [ObjectSchema, ObjectSchema] }, ObjectFromSchema | ObjectFromSchema> + createFilter, Required1 extends StringKeys, Properties2 extends Record, Required2 extends StringKeys> (schema: { oneOf: [ObjectSchema, ObjectSchema] }, options?: any): Filter<{ oneOf: [ObjectSchema, ObjectSchema] }, ObjectFromSchema | ObjectFromSchema> + , Required1 extends StringKeys, Properties2 extends Record, Required2 extends StringKeys, Properties3 extends Record, Required3 extends StringKeys> (schema: { oneOf: [ObjectSchema, ObjectSchema, ObjectSchema] }, options?: any): Validator<{ oneOf: [ObjectSchema, ObjectSchema, ObjectSchema] }, ObjectFromSchema | ObjectFromSchema | ObjectFromSchema> + createFilter, Required1 extends StringKeys, Properties2 extends Record, Required2 extends StringKeys, Properties3 extends Record, Required3 extends StringKeys> (schema: { oneOf: [ObjectSchema, ObjectSchema, ObjectSchema] }, options?: any): Filter<{ oneOf: [ObjectSchema, ObjectSchema, ObjectSchema] }, ObjectFromSchema | ObjectFromSchema | ObjectFromSchema> + , Required1 extends StringKeys, Properties2 extends Record, Required2 extends StringKeys, Properties3 extends Record, Required3 extends StringKeys, Properties4 extends Record, Required4 extends StringKeys> (schema: { oneOf: [ObjectSchema, ObjectSchema, ObjectSchema, ObjectSchema] }, options?: any): Validator<{ oneOf: [ObjectSchema, ObjectSchema, ObjectSchema, ObjectSchema] }, ObjectFromSchema | ObjectFromSchema | ObjectFromSchema| ObjectFromSchema> + createFilter, Required1 extends StringKeys, Properties2 extends Record, Required2 extends StringKeys, Properties3 extends Record, Required3 extends StringKeys, Properties4 extends Record, Required4 extends StringKeys> (schema: { oneOf: [ObjectSchema, ObjectSchema, ObjectSchema, ObjectSchema] }, options?: any): Filter<{ oneOf: [ObjectSchema, ObjectSchema, ObjectSchema, ObjectSchema] }, ObjectFromSchema | ObjectFromSchema | ObjectFromSchema| ObjectFromSchema> + + /* One of plain schema */ + (schema: { oneOf: [Schema1, Schema2] }, options?: any): Validator<{ oneOf: [Schema1, Schema2] }, TypeFromSchema | TypeFromSchema> + createFilter (schema: { oneOf: [Schema1, Schema2] }, options?: any): Filter<{ oneOf: [Schema1, Schema2] }, TypeFromSchema | TypeFromSchema> + (schema: { oneOf: [Schema1, Schema2, Schema3] }, options?: any): Validator<{ oneOf: [Schema1, Schema2, Schema3] }, TypeFromSchema | TypeFromSchema | TypeFromSchema> + createFilter (schema: { oneOf: [Schema1, Schema2, Schema3] }, options?: any): Filter<{ oneOf: [Schema1, Schema2, Schema3] }, TypeFromSchema | TypeFromSchema | TypeFromSchema> + (schema: { oneOf: [Schema1, Schema2, Schema3, Schema4] }, options?: any): Validator<{ oneOf: [Schema1, Schema2, Schema3, Schema4] }, TypeFromSchema | TypeFromSchema | TypeFromSchema | TypeFromSchema> + createFilter (schema: { oneOf: [Schema1, Schema2, Schema3, Schema4] }, options?: any): Filter<{ oneOf: [Schema1, Schema2, Schema3, Schema4] }, TypeFromSchema | TypeFromSchema | TypeFromSchema | TypeFromSchema> + + /* Object schema */ + , Required extends StringKeys> (schema: ObjectSchema, options?: any): Validator> + createFilter, Required extends StringKeys> (schema: ObjectSchema, options?: any): Filter> + + /* Plain schema */ + (schema: Schema, options?: any): Validator + createFilter (schema: Schema, options?: any): Filter } declare const factory: Factory diff --git a/test/typings.ts b/test/typings.ts index 4ccf903..ccbf0c3 100644 --- a/test/typings.ts +++ b/test/typings.ts @@ -204,3 +204,112 @@ if (noRequiredFieldsValidator(input)) { if (typeof input.b !== 'undefined') assertType(input.b) if (typeof input.c !== 'undefined') assertType(input.c) } + +const animalValidator = createValidator({ + oneOf: [ + { + type: 'object', + properties: { + type: { enum: ['cat' as 'cat'] }, + name: { type: 'string' } + }, + required: [ + 'type', + 'name' + ] + }, + { + type: 'object', + properties: { + type: { enum: ['dog' as 'dog'] }, + name: { type: 'string' } + }, + required: [ + 'type', + 'name' + ] + } + ] +}) + +if (animalValidator(input)) { + if (input.type !== 'cat') assertType<'dog'>(input.type) + if (input.type !== 'dog') assertType<'cat'>(input.type) + assertType(input.name) +} + +const shapeValidator = createValidator({ + oneOf: [ + { type: 'object', properties: { kind: { enum: ['triangle' as 'triangle'] } }, required: ['kind'] }, + { type: 'object', properties: { kind: { enum: ['rectangle' as 'rectangle'] } }, required: ['kind'] }, + { type: 'object', properties: { kind: { enum: ['circle' as 'circle'] } }, required: ['kind'] }, + ] +}) + +if (shapeValidator(input)) { + if (input.kind !== 'triangle' && input.kind !== 'rectangle') assertType<'circle'>(input.kind) + if (input.kind !== 'rectangle' && input.kind !== 'circle') assertType<'triangle'>(input.kind) + if (input.kind !== 'circle' && input.kind !== 'triangle') assertType<'rectangle'>(input.kind) +} + +const foobar = createValidator({ + oneOf: [ + { type: 'object', properties: { a: { type: 'string' } }, required: ['a'] }, + { type: 'object', properties: { b: { type: 'number' } }, required: ['b'] }, + { type: 'object', properties: { c: { type: 'boolean' } }, required: ['c'] }, + { type: 'object', properties: { d: { type: 'null' } }, required: ['d'] }, + ] +}) + +if (foobar(input)) { + if ('a' in input) assertType(input.a) + if ('b' in input) assertType(input.b) + if ('c' in input) assertType(input.c) + if ('d' in input) assertType(input.d) +} + +const stringOrNullValidator = createValidator({ + oneOf: [ + { type: 'string' }, + { type: 'null' } + ] +}) + +if (stringOrNullValidator(input)) { + if (typeof input !== 'object') assertType(input) + if (typeof input !== 'string') assertType(input) +} + +const primitiveValidator = createValidator({ + oneOf: [ + { type: 'string' }, + { type: 'number' }, + { type: 'boolean' } + ] +}) + +if (primitiveValidator(input)) { + if (typeof input !== 'string' && typeof input !== 'number') assertType(input) + if (typeof input !== 'number' && typeof input !== 'boolean') assertType(input) + if (typeof input !== 'boolean' && typeof input !== 'string') assertType(input) +} + +const overengineeredColorValidator = createValidator({ + oneOf: [ + { enum: ['red' as 'red', 'pink' as 'pink'] }, + { enum: ['green' as 'green', 'olive' as 'olive'] }, + { enum: ['blue' as 'blue', 'teal' as 'teal'] }, + { enum: ['yellow' as 'yellow', 'cream' as 'cream'] } + ] +}) + +if (overengineeredColorValidator(input)) { + if (input !== 'red' && input !== 'pink' && input !== 'green' && input !== 'olive' && input !== 'blue' && input !== 'teal' && input !== 'yellow') assertType<'cream'>(input) + if (input !== 'pink' && input !== 'green' && input !== 'olive' && input !== 'blue' && input !== 'teal' && input !== 'yellow' && input !== 'cream') assertType<'red'>(input) + if (input !== 'green' && input !== 'olive' && input !== 'blue' && input !== 'teal' && input !== 'yellow' && input !== 'cream' && input !== 'red') assertType<'pink'>(input) + if (input !== 'olive' && input !== 'blue' && input !== 'teal' && input !== 'yellow' && input !== 'cream' && input !== 'red' && input !== 'pink') assertType<'green'>(input) + if (input !== 'blue' && input !== 'teal' && input !== 'yellow' && input !== 'cream' && input !== 'red' && input !== 'pink' && input !== 'green') assertType<'olive'>(input) + if (input !== 'teal' && input !== 'yellow' && input !== 'cream' && input !== 'red' && input !== 'pink' && input !== 'green' && input !== 'olive') assertType<'blue'>(input) + if (input !== 'yellow' && input !== 'cream' && input !== 'red' && input !== 'pink' && input !== 'green' && input !== 'olive' && input !== 'blue') assertType<'teal'>(input) + if (input !== 'cream' && input !== 'red' && input !== 'pink' && input !== 'green' && input !== 'olive' && input !== 'blue' && input !== 'teal') assertType<'yellow'>(input) +}