Skip to content

Commit e7cf37e

Browse files
committed
feat: add j.tuple
1 parent 9a91f9c commit e7cf37e

File tree

3 files changed

+78
-0
lines changed

3 files changed

+78
-0
lines changed

packages/nodejs-lib/src/validation/ajv/ajv.validations.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2090,6 +2090,40 @@ describe('array', () => {
20902090
})
20912091
})
20922092

2093+
describe('tuple', () => {
2094+
test('should work correctly with type inference', () => {
2095+
const schema = j.tuple([j.string().nullable(), j.number(), j.boolean()])
2096+
2097+
const [err, result] = AjvSchema.create(schema).getValidationResult(['foo', 1, true])
2098+
2099+
expect(err).toBeNull()
2100+
expect(result).toEqual(['foo', 1, true])
2101+
expectTypeOf(result).toExtend<[string | null, number, boolean]>()
2102+
})
2103+
2104+
test('should accept valid data', () => {
2105+
const schema = j.tuple([j.string().minLength(3).nullable(), j.number(), j.boolean()])
2106+
const ajvSchema = AjvSchema.create(schema)
2107+
2108+
const testCases: any[] = [
2109+
['foo', 1, true],
2110+
[null, 2, false],
2111+
]
2112+
testCases.forEach(input => {
2113+
const [err, result] = ajvSchema.getValidationResult(input)
2114+
2115+
expect(err, String(input)).toBeNull()
2116+
expect(result).toEqual(input)
2117+
})
2118+
2119+
const invalidCases: any[] = [[undefined, 1, true], ['fo', 1, true], 'foo', 0, true, {}, []]
2120+
invalidCases.forEach(input => {
2121+
const [err] = ajvSchema.getValidationResult(input)
2122+
expect(err, String(input)).not.toBeNull()
2123+
})
2124+
})
2125+
})
2126+
20932127
describe('set', () => {
20942128
test('should work correctly with type inference', () => {
20952129
const schema = j.set(j.string())

packages/nodejs-lib/src/validation/ajv/jsonSchemaBuilder.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,18 @@ describe('array', () => {
613613
})
614614
})
615615

616+
describe('tuple', () => {
617+
test('should correctly infer the type', () => {
618+
const schema1 = j.tuple([j.string(), j.number(), j.boolean()])
619+
expectTypeOf(schema1.in).toEqualTypeOf<[string, number, boolean]>()
620+
expectTypeOf(schema1.out).toEqualTypeOf<[string, number, boolean]>()
621+
622+
const schema2 = j.tuple([j.string().optional(), j.number(), j.boolean()])
623+
expectTypeOf(schema2.in).toEqualTypeOf<[string | undefined, number, boolean]>()
624+
expectTypeOf(schema2.out).toEqualTypeOf<[string | undefined, number, boolean]>()
625+
})
626+
})
627+
616628
describe('enum', () => {
617629
test('should correctly infer the type - for NumberEnums', () => {
618630
enum Foo {

packages/nodejs-lib/src/validation/ajv/jsonSchemaBuilder.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ export const j = {
126126
return new JsonSchemaArrayBuilder(itemSchema)
127127
},
128128

129+
tuple<const S extends JsonSchemaAnyBuilder<any, any, any>[]>(
130+
items: S,
131+
): JsonSchemaTupleBuilder<S> {
132+
return new JsonSchemaTupleBuilder<S>(items)
133+
},
134+
129135
set<IN, OUT, Opt>(
130136
itemSchema: JsonSchemaAnyBuilder<IN, OUT, Opt>,
131137
): JsonSchemaSet2Builder<IN, OUT, Opt> {
@@ -1139,6 +1145,23 @@ export class JsonSchemaEnumBuilder<
11391145
}
11401146
}
11411147

1148+
export class JsonSchemaTupleBuilder<
1149+
ITEMS extends JsonSchemaAnyBuilder<any, any, any>[],
1150+
> extends JsonSchemaAnyBuilder<TupleIn<ITEMS>, TupleOut<ITEMS>, false> {
1151+
private readonly _items: ITEMS
1152+
1153+
constructor(items: ITEMS) {
1154+
super({
1155+
type: 'array',
1156+
prefixItems: items.map(i => i.build()),
1157+
minItems: items.length,
1158+
maxItems: items.length,
1159+
})
1160+
1161+
this._items = items
1162+
}
1163+
}
1164+
11421165
type EnumBaseType = 'string' | 'number' | 'other'
11431166

11441167
export interface JsonSchema<IN = unknown, OUT = IN> {
@@ -1156,6 +1179,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
11561179

11571180
type?: string | string[]
11581181
items?: JsonSchema
1182+
prefixItems?: JsonSchema[]
11591183
properties?: {
11601184
[K in keyof IN & keyof OUT]: JsonSchema<IN[K], OUT[K]>
11611185
}
@@ -1421,3 +1445,11 @@ type SchemaIn<S> = S extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : nev
14211445
type SchemaOut<S> = S extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never
14221446
type SchemaOpt<S> =
14231447
S extends JsonSchemaAnyBuilder<any, any, infer Opt> ? (Opt extends true ? true : false) : false
1448+
1449+
type TupleIn<T extends readonly JsonSchemaAnyBuilder<any, any, any>[]> = {
1450+
[K in keyof T]: T[K] extends JsonSchemaAnyBuilder<infer I, any, any> ? I : never
1451+
}
1452+
1453+
type TupleOut<T extends readonly JsonSchemaAnyBuilder<any, any, any>[]> = {
1454+
[K in keyof T]: T[K] extends JsonSchemaAnyBuilder<any, infer O, any> ? O : never
1455+
}

0 commit comments

Comments
 (0)