Skip to content

Commit 4bec868

Browse files
authoredAug 10, 2018
Merge pull request #168 from mafintosh/ts-oneof
Add support for "oneOf" to TypeScript typings
2 parents 7160756 + e8c30d5 commit 4bec868

File tree

2 files changed

+136
-7
lines changed

2 files changed

+136
-7
lines changed
 

‎index.d.ts

+27-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type AnySchema = NullSchema | BooleanSchema | NumberSchema | StringSchema | AnyEnumSchema | AnyArraySchema | AnyObjectSchema | AnyAllOptionalObjectSchema
1+
type AnySchema = NullSchema | BooleanSchema | NumberSchema | StringSchema | AnyEnumSchema | AnyArraySchema | AnyObjectSchema | AnyAllOptionalObjectSchema | AnyOneOfSchema
22
type StringKeys<T> = (keyof T) & string
33

44
interface NullSchema {
@@ -43,6 +43,8 @@ interface AllOptionalObjectSchema<Properties extends Record<string, AnySchema>>
4343
properties: Properties
4444
}
4545

46+
interface AnyOneOfSchema { oneOf: AnySchema[] }
47+
4648
interface ArrayFromSchema<ItemSchema extends AnySchema> extends Array<TypeFromSchema<ItemSchema>> {}
4749

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

79-
interface Filter<Output> {
81+
interface Filter<Schema extends AnySchema, Output = TypeFromSchema<Schema>> {
8082
(input: Output, options?: any): Output
8183
}
8284

8385
interface Factory {
84-
<Properties extends Record<string, AnySchema>, Required extends StringKeys<Properties>> (schema: ObjectSchema<Properties, Required>, options?: any): Validator<ObjectSchema<Properties, Required>>
85-
<Schema extends AnySchema> (schema: Schema, options?: any): Validator<Schema>
86-
87-
createFilter<Properties extends Record<string, AnySchema>, Required extends StringKeys<Properties>> (schema: ObjectSchema<Properties, Required>, options?: any): Filter<ObjectFromSchema<Properties, Required>>
88-
createFilter<Schema extends AnySchema> (schema: Schema, options?: any): Filter<TypeFromSchema<Schema>>
86+
/* One of object schema */
87+
<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>>
88+
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>>
89+
<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>>
90+
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>>
91+
<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>>
92+
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>>
93+
94+
/* One of plain schema */
95+
<Schema1 extends AnySchema, Schema2 extends AnySchema> (schema: { oneOf: [Schema1, Schema2] }, options?: any): Validator<{ oneOf: [Schema1, Schema2] }, TypeFromSchema<Schema1> | TypeFromSchema<Schema2>>
96+
createFilter<Schema1 extends AnySchema, Schema2 extends AnySchema> (schema: { oneOf: [Schema1, Schema2] }, options?: any): Filter<{ oneOf: [Schema1, Schema2] }, TypeFromSchema<Schema1> | TypeFromSchema<Schema2>>
97+
<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>>
98+
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>>
99+
<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>>
100+
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>>
101+
102+
/* Object schema */
103+
<Properties extends Record<string, AnySchema>, Required extends StringKeys<Properties>> (schema: ObjectSchema<Properties, Required>, options?: any): Validator<ObjectSchema<Properties, Required>>
104+
createFilter<Properties extends Record<string, AnySchema>, Required extends StringKeys<Properties>> (schema: ObjectSchema<Properties, Required>, options?: any): Filter<ObjectSchema<Properties, Required>>
105+
106+
/* Plain schema */
107+
<Schema extends AnySchema> (schema: Schema, options?: any): Validator<Schema>
108+
createFilter<Schema extends AnySchema> (schema: Schema, options?: any): Filter<Schema>
89109
}
90110

91111
declare const factory: Factory

‎test/typings.ts

+109
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,112 @@ if (noRequiredFieldsValidator(input)) {
204204
if (typeof input.b !== 'undefined') assertType<string>(input.b)
205205
if (typeof input.c !== 'undefined') assertType<string>(input.c)
206206
}
207+
208+
const animalValidator = createValidator({
209+
oneOf: [
210+
{
211+
type: 'object',
212+
properties: {
213+
type: { enum: ['cat' as 'cat'] },
214+
name: { type: 'string' }
215+
},
216+
required: [
217+
'type',
218+
'name'
219+
]
220+
},
221+
{
222+
type: 'object',
223+
properties: {
224+
type: { enum: ['dog' as 'dog'] },
225+
name: { type: 'string' }
226+
},
227+
required: [
228+
'type',
229+
'name'
230+
]
231+
}
232+
]
233+
})
234+
235+
if (animalValidator(input)) {
236+
if (input.type !== 'cat') assertType<'dog'>(input.type)
237+
if (input.type !== 'dog') assertType<'cat'>(input.type)
238+
assertType<string>(input.name)
239+
}
240+
241+
const shapeValidator = createValidator({
242+
oneOf: [
243+
{ type: 'object', properties: { kind: { enum: ['triangle' as 'triangle'] } }, required: ['kind'] },
244+
{ type: 'object', properties: { kind: { enum: ['rectangle' as 'rectangle'] } }, required: ['kind'] },
245+
{ type: 'object', properties: { kind: { enum: ['circle' as 'circle'] } }, required: ['kind'] },
246+
]
247+
})
248+
249+
if (shapeValidator(input)) {
250+
if (input.kind !== 'triangle' && input.kind !== 'rectangle') assertType<'circle'>(input.kind)
251+
if (input.kind !== 'rectangle' && input.kind !== 'circle') assertType<'triangle'>(input.kind)
252+
if (input.kind !== 'circle' && input.kind !== 'triangle') assertType<'rectangle'>(input.kind)
253+
}
254+
255+
const foobar = createValidator({
256+
oneOf: [
257+
{ type: 'object', properties: { a: { type: 'string' } }, required: ['a'] },
258+
{ type: 'object', properties: { b: { type: 'number' } }, required: ['b'] },
259+
{ type: 'object', properties: { c: { type: 'boolean' } }, required: ['c'] },
260+
{ type: 'object', properties: { d: { type: 'null' } }, required: ['d'] },
261+
]
262+
})
263+
264+
if (foobar(input)) {
265+
if ('a' in input) assertType<string>(input.a)
266+
if ('b' in input) assertType<number>(input.b)
267+
if ('c' in input) assertType<boolean>(input.c)
268+
if ('d' in input) assertType<null>(input.d)
269+
}
270+
271+
const stringOrNullValidator = createValidator({
272+
oneOf: [
273+
{ type: 'string' },
274+
{ type: 'null' }
275+
]
276+
})
277+
278+
if (stringOrNullValidator(input)) {
279+
if (typeof input !== 'object') assertType<string>(input)
280+
if (typeof input !== 'string') assertType<null>(input)
281+
}
282+
283+
const primitiveValidator = createValidator({
284+
oneOf: [
285+
{ type: 'string' },
286+
{ type: 'number' },
287+
{ type: 'boolean' }
288+
]
289+
})
290+
291+
if (primitiveValidator(input)) {
292+
if (typeof input !== 'string' && typeof input !== 'number') assertType<boolean>(input)
293+
if (typeof input !== 'number' && typeof input !== 'boolean') assertType<string>(input)
294+
if (typeof input !== 'boolean' && typeof input !== 'string') assertType<number>(input)
295+
}
296+
297+
const overengineeredColorValidator = createValidator({
298+
oneOf: [
299+
{ enum: ['red' as 'red', 'pink' as 'pink'] },
300+
{ enum: ['green' as 'green', 'olive' as 'olive'] },
301+
{ enum: ['blue' as 'blue', 'teal' as 'teal'] },
302+
{ enum: ['yellow' as 'yellow', 'cream' as 'cream'] }
303+
]
304+
})
305+
306+
if (overengineeredColorValidator(input)) {
307+
if (input !== 'red' && input !== 'pink' && input !== 'green' && input !== 'olive' && input !== 'blue' && input !== 'teal' && input !== 'yellow') assertType<'cream'>(input)
308+
if (input !== 'pink' && input !== 'green' && input !== 'olive' && input !== 'blue' && input !== 'teal' && input !== 'yellow' && input !== 'cream') assertType<'red'>(input)
309+
if (input !== 'green' && input !== 'olive' && input !== 'blue' && input !== 'teal' && input !== 'yellow' && input !== 'cream' && input !== 'red') assertType<'pink'>(input)
310+
if (input !== 'olive' && input !== 'blue' && input !== 'teal' && input !== 'yellow' && input !== 'cream' && input !== 'red' && input !== 'pink') assertType<'green'>(input)
311+
if (input !== 'blue' && input !== 'teal' && input !== 'yellow' && input !== 'cream' && input !== 'red' && input !== 'pink' && input !== 'green') assertType<'olive'>(input)
312+
if (input !== 'teal' && input !== 'yellow' && input !== 'cream' && input !== 'red' && input !== 'pink' && input !== 'green' && input !== 'olive') assertType<'blue'>(input)
313+
if (input !== 'yellow' && input !== 'cream' && input !== 'red' && input !== 'pink' && input !== 'green' && input !== 'olive' && input !== 'blue') assertType<'teal'>(input)
314+
if (input !== 'cream' && input !== 'red' && input !== 'pink' && input !== 'green' && input !== 'olive' && input !== 'blue' && input !== 'teal') assertType<'yellow'>(input)
315+
}

0 commit comments

Comments
 (0)
Please sign in to comment.