Skip to content

Commit

Permalink
Add support for "oneOf" to TypeScript typings
Browse files Browse the repository at this point in the history
  • Loading branch information
LinusU committed Aug 10, 2018
1 parent 7160756 commit e8c30d5
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 7 deletions.
34 changes: 27 additions & 7 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -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<T> = (keyof T) & string

interface NullSchema {
Expand Down Expand Up @@ -43,6 +43,8 @@ interface AllOptionalObjectSchema<Properties extends Record<string, AnySchema>>
properties: Properties
}

interface AnyOneOfSchema { oneOf: AnySchema[] }

interface ArrayFromSchema<ItemSchema extends AnySchema> extends Array<TypeFromSchema<ItemSchema>> {}

type ObjectFromSchema<Properties extends Record<string, AnySchema>, Required extends StringKeys<Properties>> = {
Expand Down Expand Up @@ -76,16 +78,34 @@ interface Validator<Schema extends AnySchema, Output = TypeFromSchema<Schema>> {
toJSON(): Schema
}

interface Filter<Output> {
interface Filter<Schema extends AnySchema, Output = TypeFromSchema<Schema>> {
(input: Output, options?: any): Output
}

interface Factory {
<Properties extends Record<string, AnySchema>, Required extends StringKeys<Properties>> (schema: ObjectSchema<Properties, Required>, options?: any): Validator<ObjectSchema<Properties, Required>>
<Schema extends AnySchema> (schema: Schema, options?: any): Validator<Schema>

createFilter<Properties extends Record<string, AnySchema>, Required extends StringKeys<Properties>> (schema: ObjectSchema<Properties, Required>, options?: any): Filter<ObjectFromSchema<Properties, Required>>
createFilter<Schema extends AnySchema> (schema: Schema, options?: any): Filter<TypeFromSchema<Schema>>
/* One of object schema */
<Properties1 extends Record<string, AnySchema>, Required1 extends StringKeys<Properties1>, Properties2 extends Record<string, AnySchema>, Required2 extends StringKeys<Properties2>> (schema: { oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>] }, options?: any): Validator<{ oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>] }, ObjectFromSchema<Properties1, Required1> | ObjectFromSchema<Properties2, Required2>>
createFilter<Properties1 extends Record<string, AnySchema>, Required1 extends StringKeys<Properties1>, Properties2 extends Record<string, AnySchema>, Required2 extends StringKeys<Properties2>> (schema: { oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>] }, options?: any): Filter<{ oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>] }, ObjectFromSchema<Properties1, Required1> | ObjectFromSchema<Properties2, Required2>>
<Properties1 extends Record<string, AnySchema>, Required1 extends StringKeys<Properties1>, Properties2 extends Record<string, AnySchema>, Required2 extends StringKeys<Properties2>, Properties3 extends Record<string, AnySchema>, Required3 extends StringKeys<Properties3>> (schema: { oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>, ObjectSchema<Properties3, Required3>] }, options?: any): Validator<{ oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>, ObjectSchema<Properties3, Required3>] }, ObjectFromSchema<Properties1, Required1> | ObjectFromSchema<Properties2, Required2> | ObjectFromSchema<Properties3, Required3>>
createFilter<Properties1 extends Record<string, AnySchema>, Required1 extends StringKeys<Properties1>, Properties2 extends Record<string, AnySchema>, Required2 extends StringKeys<Properties2>, Properties3 extends Record<string, AnySchema>, Required3 extends StringKeys<Properties3>> (schema: { oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>, ObjectSchema<Properties3, Required3>] }, options?: any): Filter<{ oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>, ObjectSchema<Properties3, Required3>] }, ObjectFromSchema<Properties1, Required1> | ObjectFromSchema<Properties2, Required2> | ObjectFromSchema<Properties3, Required3>>
<Properties1 extends Record<string, AnySchema>, Required1 extends StringKeys<Properties1>, Properties2 extends Record<string, AnySchema>, Required2 extends StringKeys<Properties2>, Properties3 extends Record<string, AnySchema>, Required3 extends StringKeys<Properties3>, Properties4 extends Record<string, AnySchema>, Required4 extends StringKeys<Properties4>> (schema: { oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>, ObjectSchema<Properties3, Required3>, ObjectSchema<Properties4, Required4>] }, options?: any): Validator<{ oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>, ObjectSchema<Properties3, Required3>, ObjectSchema<Properties4, Required4>] }, ObjectFromSchema<Properties1, Required1> | ObjectFromSchema<Properties2, Required2> | ObjectFromSchema<Properties3, Required3> | ObjectFromSchema<Properties4, Required4>>
createFilter<Properties1 extends Record<string, AnySchema>, Required1 extends StringKeys<Properties1>, Properties2 extends Record<string, AnySchema>, Required2 extends StringKeys<Properties2>, Properties3 extends Record<string, AnySchema>, Required3 extends StringKeys<Properties3>, Properties4 extends Record<string, AnySchema>, Required4 extends StringKeys<Properties4>> (schema: { oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>, ObjectSchema<Properties3, Required3>, ObjectSchema<Properties4, Required4>] }, options?: any): Filter<{ oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>, ObjectSchema<Properties3, Required3>, ObjectSchema<Properties4, Required4>] }, ObjectFromSchema<Properties1, Required1> | ObjectFromSchema<Properties2, Required2> | ObjectFromSchema<Properties3, Required3> | ObjectFromSchema<Properties4, Required4>>

/* One of plain schema */
<Schema1 extends AnySchema, Schema2 extends AnySchema> (schema: { oneOf: [Schema1, Schema2] }, options?: any): Validator<{ oneOf: [Schema1, Schema2] }, TypeFromSchema<Schema1> | TypeFromSchema<Schema2>>
createFilter<Schema1 extends AnySchema, Schema2 extends AnySchema> (schema: { oneOf: [Schema1, Schema2] }, options?: any): Filter<{ oneOf: [Schema1, Schema2] }, TypeFromSchema<Schema1> | TypeFromSchema<Schema2>>
<Schema1 extends AnySchema, Schema2 extends AnySchema, Schema3 extends AnySchema> (schema: { oneOf: [Schema1, Schema2, Schema3] }, options?: any): Validator<{ oneOf: [Schema1, Schema2, Schema3] }, TypeFromSchema<Schema1> | TypeFromSchema<Schema2> | TypeFromSchema<Schema3>>
createFilter<Schema1 extends AnySchema, Schema2 extends AnySchema, Schema3 extends AnySchema> (schema: { oneOf: [Schema1, Schema2, Schema3] }, options?: any): Filter<{ oneOf: [Schema1, Schema2, Schema3] }, TypeFromSchema<Schema1> | TypeFromSchema<Schema2> | TypeFromSchema<Schema3>>
<Schema1 extends AnySchema, Schema2 extends AnySchema, Schema3 extends AnySchema, Schema4 extends AnySchema> (schema: { oneOf: [Schema1, Schema2, Schema3, Schema4] }, options?: any): Validator<{ oneOf: [Schema1, Schema2, Schema3, Schema4] }, TypeFromSchema<Schema1> | TypeFromSchema<Schema2> | TypeFromSchema<Schema3> | TypeFromSchema<Schema4>>
createFilter<Schema1 extends AnySchema, Schema2 extends AnySchema, Schema3 extends AnySchema, Schema4 extends AnySchema> (schema: { oneOf: [Schema1, Schema2, Schema3, Schema4] }, options?: any): Filter<{ oneOf: [Schema1, Schema2, Schema3, Schema4] }, TypeFromSchema<Schema1> | TypeFromSchema<Schema2> | TypeFromSchema<Schema3> | TypeFromSchema<Schema4>>

/* Object schema */
<Properties extends Record<string, AnySchema>, Required extends StringKeys<Properties>> (schema: ObjectSchema<Properties, Required>, options?: any): Validator<ObjectSchema<Properties, Required>>
createFilter<Properties extends Record<string, AnySchema>, Required extends StringKeys<Properties>> (schema: ObjectSchema<Properties, Required>, options?: any): Filter<ObjectSchema<Properties, Required>>

/* Plain schema */
<Schema extends AnySchema> (schema: Schema, options?: any): Validator<Schema>
createFilter<Schema extends AnySchema> (schema: Schema, options?: any): Filter<Schema>
}

declare const factory: Factory
Expand Down
109 changes: 109 additions & 0 deletions test/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,112 @@ if (noRequiredFieldsValidator(input)) {
if (typeof input.b !== 'undefined') assertType<string>(input.b)
if (typeof input.c !== 'undefined') assertType<string>(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<string>(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<string>(input.a)
if ('b' in input) assertType<number>(input.b)
if ('c' in input) assertType<boolean>(input.c)
if ('d' in input) assertType<null>(input.d)
}

const stringOrNullValidator = createValidator({
oneOf: [
{ type: 'string' },
{ type: 'null' }
]
})

if (stringOrNullValidator(input)) {
if (typeof input !== 'object') assertType<string>(input)
if (typeof input !== 'string') assertType<null>(input)
}

const primitiveValidator = createValidator({
oneOf: [
{ type: 'string' },
{ type: 'number' },
{ type: 'boolean' }
]
})

if (primitiveValidator(input)) {
if (typeof input !== 'string' && typeof input !== 'number') assertType<boolean>(input)
if (typeof input !== 'number' && typeof input !== 'boolean') assertType<string>(input)
if (typeof input !== 'boolean' && typeof input !== 'string') assertType<number>(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)
}

0 comments on commit e8c30d5

Please sign in to comment.