schematox 1.2.1 → 1.2.2-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants.d.ts +20 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +22 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/parse.d.ts +5 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +328 -0
- package/dist/parse.js.map +1 -0
- package/dist/struct.d.ts +48 -0
- package/dist/struct.d.ts.map +1 -0
- package/dist/struct.js +111 -0
- package/dist/struct.js.map +1 -0
- package/dist/types/extensions.d.ts +13 -0
- package/dist/types/extensions.d.ts.map +1 -0
- package/dist/types/extensions.js +2 -0
- package/dist/types/extensions.js.map +1 -0
- package/dist/types/infer.d.ts +35 -0
- package/dist/types/infer.d.ts.map +1 -0
- package/dist/types/infer.js +2 -0
- package/dist/types/infer.js.map +1 -0
- package/dist/types/schema.d.ts +93 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +2 -0
- package/dist/types/schema.js.map +1 -0
- package/dist/types/standard-schema.d.ts +35 -0
- package/dist/types/standard-schema.d.ts.map +1 -0
- package/dist/types/standard-schema.js +2 -0
- package/dist/types/standard-schema.js.map +1 -0
- package/dist/types/struct.d.ts +52 -0
- package/dist/types/struct.d.ts.map +1 -0
- package/dist/types/struct.js +2 -0
- package/dist/types/struct.js.map +1 -0
- package/dist/types/utils.d.ts +41 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/dist/types/utils.js +2 -0
- package/dist/types/utils.js.map +1 -0
- package/dist/utils.d.ts +9 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +14 -0
- package/dist/utils.js.map +1 -0
- package/package.json +15 -4
- package/src/tests/README.md +390 -0
- package/src/tests/by-struct/array.test.ts +1684 -0
- package/src/tests/by-struct/boolean.test.ts +741 -0
- package/src/tests/by-struct/literal.test.ts +755 -0
- package/src/tests/by-struct/number.test.ts +1234 -0
- package/src/tests/by-struct/object.test.ts +1484 -0
- package/src/tests/by-struct/record.test.ts +1802 -0
- package/src/tests/by-struct/string.test.ts +1252 -0
- package/src/tests/by-struct/tuple.test.ts +1341 -0
- package/src/tests/by-struct/union.test.ts +1284 -0
- package/src/tests/fixtures.ts +52 -0
- package/src/tests/fold-constants.ts +247 -0
- package/src/tests/fold-morph.ts +49 -0
- package/src/tests/type.ts +1 -0
- package/src/tests/types/extensions.test.ts +117 -0
- package/src/tests/types/infer.test.ts +1410 -0
- package/src/tests/utils.test.ts +191 -0
- package/CHANGELOG.md +0 -52
|
@@ -0,0 +1,1802 @@
|
|
|
1
|
+
import * as x from '../../'
|
|
2
|
+
import * as fixture from '../fixtures'
|
|
3
|
+
|
|
4
|
+
import type { StructSharedKeys } from '../type'
|
|
5
|
+
|
|
6
|
+
describe('Type inference and parse by schema/construct/struct (foldA)', () => {
|
|
7
|
+
it('branded key', () => {
|
|
8
|
+
const schema = {
|
|
9
|
+
type: 'record',
|
|
10
|
+
key: { type: 'string', brand: ['x', 'y'] },
|
|
11
|
+
of: { type: 'boolean' },
|
|
12
|
+
} as const satisfies x.Schema
|
|
13
|
+
|
|
14
|
+
const struct = x.record(x.boolean(), x.string().brand('x', 'y'))
|
|
15
|
+
|
|
16
|
+
type Key = string & { __x: 'y' }
|
|
17
|
+
type ExpectedSubj = Record<Key, boolean>
|
|
18
|
+
|
|
19
|
+
const subjects: Array<ExpectedSubj> = [
|
|
20
|
+
{},
|
|
21
|
+
// @ts-expect-error must not allow not branded string key declaration
|
|
22
|
+
{ ['x']: true },
|
|
23
|
+
{ ['x' as Key]: false },
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
// @ts-expect-error must not allow not branded string property access
|
|
27
|
+
subjects[1]!['x'] = false
|
|
28
|
+
|
|
29
|
+
foldA: {
|
|
30
|
+
const construct = x.makeStruct(schema)
|
|
31
|
+
|
|
32
|
+
/* ensure that schema/construct/struct/~standard subject types are identical */
|
|
33
|
+
|
|
34
|
+
type ConstructSchemaSubj = x.Infer<typeof construct.__schema>
|
|
35
|
+
|
|
36
|
+
x.tCh<ConstructSchemaSubj, ExpectedSubj>()
|
|
37
|
+
x.tCh<ExpectedSubj, ConstructSchemaSubj>()
|
|
38
|
+
|
|
39
|
+
type SchemaSubj = x.Infer<typeof schema>
|
|
40
|
+
|
|
41
|
+
x.tCh<SchemaSubj, ExpectedSubj>()
|
|
42
|
+
x.tCh<ExpectedSubj, SchemaSubj>()
|
|
43
|
+
|
|
44
|
+
type StructSubj = x.Infer<typeof struct.__schema>
|
|
45
|
+
|
|
46
|
+
x.tCh<StructSubj, ExpectedSubj>()
|
|
47
|
+
x.tCh<ExpectedSubj, StructSubj>()
|
|
48
|
+
|
|
49
|
+
type StandardSubj = NonNullable<
|
|
50
|
+
(typeof struct)['~standard']['types']
|
|
51
|
+
>['output']
|
|
52
|
+
|
|
53
|
+
x.tCh<StandardSubj, ExpectedSubj>()
|
|
54
|
+
x.tCh<ExpectedSubj, StandardSubj>()
|
|
55
|
+
|
|
56
|
+
/* parsed either type check */
|
|
57
|
+
|
|
58
|
+
type ExpectedParsed = x.ParseResult<ExpectedSubj>
|
|
59
|
+
|
|
60
|
+
const parsed = x.parse(schema, undefined)
|
|
61
|
+
|
|
62
|
+
type SchemaParsed = typeof parsed
|
|
63
|
+
|
|
64
|
+
x.tCh<SchemaParsed, ExpectedParsed>()
|
|
65
|
+
x.tCh<ExpectedParsed, SchemaParsed>()
|
|
66
|
+
|
|
67
|
+
type ConstructParsed = ReturnType<typeof construct.parse>
|
|
68
|
+
|
|
69
|
+
x.tCh<ConstructParsed, ExpectedParsed>()
|
|
70
|
+
x.tCh<ExpectedParsed, ConstructParsed>()
|
|
71
|
+
|
|
72
|
+
type StructParsed = ReturnType<typeof struct.parse>
|
|
73
|
+
|
|
74
|
+
x.tCh<StructParsed, ExpectedParsed>()
|
|
75
|
+
x.tCh<ExpectedParsed, StructParsed>()
|
|
76
|
+
|
|
77
|
+
type StandardParsed = Extract<
|
|
78
|
+
ReturnType<(typeof struct)['~standard']['validate']>,
|
|
79
|
+
{ value: unknown }
|
|
80
|
+
>['value']
|
|
81
|
+
|
|
82
|
+
x.tCh<StandardParsed, ExpectedSubj>()
|
|
83
|
+
x.tCh<ExpectedSubj, StandardParsed>()
|
|
84
|
+
|
|
85
|
+
/* runtime schema check */
|
|
86
|
+
|
|
87
|
+
expect(struct.__schema).toStrictEqual(schema)
|
|
88
|
+
expect(construct.__schema).toStrictEqual(schema)
|
|
89
|
+
expect(construct.__schema === schema).toBe(false)
|
|
90
|
+
|
|
91
|
+
/* parse result check */
|
|
92
|
+
|
|
93
|
+
for (const subj of subjects) {
|
|
94
|
+
const schemaParsed = x.parse(schema, subj)
|
|
95
|
+
|
|
96
|
+
expect(schemaParsed.error).toBe(undefined)
|
|
97
|
+
expect(schemaParsed.data).toStrictEqual(subj)
|
|
98
|
+
|
|
99
|
+
const constructParsed = construct.parse(subj)
|
|
100
|
+
|
|
101
|
+
expect(constructParsed.error).toBe(undefined)
|
|
102
|
+
expect(constructParsed.data).toStrictEqual(subj)
|
|
103
|
+
|
|
104
|
+
const structParsed = struct.parse(subj)
|
|
105
|
+
|
|
106
|
+
expect(structParsed.error).toBe(undefined)
|
|
107
|
+
expect(structParsed.data).toStrictEqual(subj)
|
|
108
|
+
|
|
109
|
+
const standardParsed = struct['~standard'].validate(subj)
|
|
110
|
+
|
|
111
|
+
if (standardParsed instanceof Promise) {
|
|
112
|
+
throw Error('Not expected')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (standardParsed.issues !== undefined) {
|
|
116
|
+
throw Error('not expected')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
expect(standardParsed.value).toStrictEqual(subj)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('required', () => {
|
|
125
|
+
const schema = {
|
|
126
|
+
type: 'record',
|
|
127
|
+
of: { type: 'boolean' },
|
|
128
|
+
} as const satisfies x.Schema
|
|
129
|
+
|
|
130
|
+
const struct = x.record(x.boolean())
|
|
131
|
+
|
|
132
|
+
type ExpectedSubj = Record<string, boolean>
|
|
133
|
+
|
|
134
|
+
const subjects: Array<ExpectedSubj> = [{}, { x: true }, { x: false }]
|
|
135
|
+
|
|
136
|
+
foldA: {
|
|
137
|
+
const construct = x.makeStruct(schema)
|
|
138
|
+
|
|
139
|
+
/* ensure that schema/construct/struct/~standard subject types are identical */
|
|
140
|
+
|
|
141
|
+
type ConstructSchemaSubj = x.Infer<typeof construct.__schema>
|
|
142
|
+
|
|
143
|
+
x.tCh<ConstructSchemaSubj, ExpectedSubj>()
|
|
144
|
+
x.tCh<ExpectedSubj, ConstructSchemaSubj>()
|
|
145
|
+
|
|
146
|
+
type SchemaSubj = x.Infer<typeof schema>
|
|
147
|
+
|
|
148
|
+
x.tCh<SchemaSubj, ExpectedSubj>()
|
|
149
|
+
x.tCh<ExpectedSubj, SchemaSubj>()
|
|
150
|
+
|
|
151
|
+
type StructSubj = x.Infer<typeof struct.__schema>
|
|
152
|
+
|
|
153
|
+
x.tCh<StructSubj, ExpectedSubj>()
|
|
154
|
+
x.tCh<ExpectedSubj, StructSubj>()
|
|
155
|
+
|
|
156
|
+
type StandardSubj = NonNullable<
|
|
157
|
+
(typeof struct)['~standard']['types']
|
|
158
|
+
>['output']
|
|
159
|
+
|
|
160
|
+
x.tCh<StandardSubj, ExpectedSubj>()
|
|
161
|
+
x.tCh<ExpectedSubj, StandardSubj>()
|
|
162
|
+
|
|
163
|
+
/* parsed either type check */
|
|
164
|
+
|
|
165
|
+
type ExpectedParsed = x.ParseResult<ExpectedSubj>
|
|
166
|
+
|
|
167
|
+
const parsed = x.parse(schema, undefined)
|
|
168
|
+
|
|
169
|
+
type SchemaParsed = typeof parsed
|
|
170
|
+
|
|
171
|
+
x.tCh<SchemaParsed, ExpectedParsed>()
|
|
172
|
+
x.tCh<ExpectedParsed, SchemaParsed>()
|
|
173
|
+
|
|
174
|
+
type ConstructParsed = ReturnType<typeof construct.parse>
|
|
175
|
+
|
|
176
|
+
x.tCh<ConstructParsed, ExpectedParsed>()
|
|
177
|
+
x.tCh<ExpectedParsed, ConstructParsed>()
|
|
178
|
+
|
|
179
|
+
type StructParsed = ReturnType<typeof struct.parse>
|
|
180
|
+
|
|
181
|
+
x.tCh<StructParsed, ExpectedParsed>()
|
|
182
|
+
x.tCh<ExpectedParsed, StructParsed>()
|
|
183
|
+
|
|
184
|
+
type StandardParsed = Extract<
|
|
185
|
+
ReturnType<(typeof struct)['~standard']['validate']>,
|
|
186
|
+
{ value: unknown }
|
|
187
|
+
>['value']
|
|
188
|
+
|
|
189
|
+
x.tCh<StandardParsed, ExpectedSubj>()
|
|
190
|
+
x.tCh<ExpectedSubj, StandardParsed>()
|
|
191
|
+
|
|
192
|
+
/* runtime schema check */
|
|
193
|
+
|
|
194
|
+
expect(struct.__schema).toStrictEqual(schema)
|
|
195
|
+
expect(construct.__schema).toStrictEqual(schema)
|
|
196
|
+
expect(construct.__schema === schema).toBe(false)
|
|
197
|
+
|
|
198
|
+
/* parse result check */
|
|
199
|
+
|
|
200
|
+
for (const subj of subjects) {
|
|
201
|
+
const schemaParsed = x.parse(schema, subj)
|
|
202
|
+
|
|
203
|
+
expect(schemaParsed.error).toBe(undefined)
|
|
204
|
+
expect(schemaParsed.data).toStrictEqual(subj)
|
|
205
|
+
|
|
206
|
+
const constructParsed = construct.parse(subj)
|
|
207
|
+
|
|
208
|
+
expect(constructParsed.error).toBe(undefined)
|
|
209
|
+
expect(constructParsed.data).toStrictEqual(subj)
|
|
210
|
+
|
|
211
|
+
const structParsed = struct.parse(subj)
|
|
212
|
+
|
|
213
|
+
expect(structParsed.error).toBe(undefined)
|
|
214
|
+
expect(structParsed.data).toStrictEqual(subj)
|
|
215
|
+
|
|
216
|
+
const standardParsed = struct['~standard'].validate(subj)
|
|
217
|
+
|
|
218
|
+
if (standardParsed instanceof Promise) {
|
|
219
|
+
throw Error('Not expected')
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (standardParsed.issues !== undefined) {
|
|
223
|
+
throw Error('not expected')
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
expect(standardParsed.value).toStrictEqual(subj)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('optional', () => {
|
|
232
|
+
const schema = {
|
|
233
|
+
type: 'record',
|
|
234
|
+
of: { type: 'boolean' },
|
|
235
|
+
optional: true,
|
|
236
|
+
} as const satisfies x.Schema
|
|
237
|
+
|
|
238
|
+
const struct = x.record(x.boolean()).optional()
|
|
239
|
+
|
|
240
|
+
type ExpectedSubj = Record<string, boolean> | undefined
|
|
241
|
+
|
|
242
|
+
const subjects: Array<ExpectedSubj> = [{}, { x: true }]
|
|
243
|
+
|
|
244
|
+
foldA: {
|
|
245
|
+
const construct = x.makeStruct(schema)
|
|
246
|
+
|
|
247
|
+
/* ensure that schema/construct/struct/~standard subject types are identical */
|
|
248
|
+
|
|
249
|
+
type ConstructSchemaSubj = x.Infer<typeof construct.__schema>
|
|
250
|
+
|
|
251
|
+
x.tCh<ConstructSchemaSubj, ExpectedSubj>()
|
|
252
|
+
x.tCh<ExpectedSubj, ConstructSchemaSubj>()
|
|
253
|
+
|
|
254
|
+
type SchemaSubj = x.Infer<typeof schema>
|
|
255
|
+
|
|
256
|
+
x.tCh<SchemaSubj, ExpectedSubj>()
|
|
257
|
+
x.tCh<ExpectedSubj, SchemaSubj>()
|
|
258
|
+
|
|
259
|
+
type StructSubj = x.Infer<typeof struct.__schema>
|
|
260
|
+
|
|
261
|
+
x.tCh<StructSubj, ExpectedSubj>()
|
|
262
|
+
x.tCh<ExpectedSubj, StructSubj>()
|
|
263
|
+
|
|
264
|
+
type StandardSubj = NonNullable<
|
|
265
|
+
(typeof struct)['~standard']['types']
|
|
266
|
+
>['output']
|
|
267
|
+
|
|
268
|
+
x.tCh<StandardSubj, ExpectedSubj>()
|
|
269
|
+
x.tCh<ExpectedSubj, StandardSubj>()
|
|
270
|
+
|
|
271
|
+
/* parsed either type check */
|
|
272
|
+
|
|
273
|
+
type ExpectedParsed = x.ParseResult<ExpectedSubj>
|
|
274
|
+
|
|
275
|
+
const parsed = x.parse(schema, undefined)
|
|
276
|
+
|
|
277
|
+
type SchemaParsed = typeof parsed
|
|
278
|
+
|
|
279
|
+
x.tCh<SchemaParsed, ExpectedParsed>()
|
|
280
|
+
x.tCh<ExpectedParsed, SchemaParsed>()
|
|
281
|
+
|
|
282
|
+
type ConstructParsed = ReturnType<typeof construct.parse>
|
|
283
|
+
|
|
284
|
+
x.tCh<ConstructParsed, ExpectedParsed>()
|
|
285
|
+
x.tCh<ExpectedParsed, ConstructParsed>()
|
|
286
|
+
|
|
287
|
+
type StructParsed = ReturnType<typeof struct.parse>
|
|
288
|
+
|
|
289
|
+
x.tCh<StructParsed, ExpectedParsed>()
|
|
290
|
+
x.tCh<ExpectedParsed, StructParsed>()
|
|
291
|
+
|
|
292
|
+
type StandardParsed = Extract<
|
|
293
|
+
ReturnType<(typeof struct)['~standard']['validate']>,
|
|
294
|
+
{ value: unknown }
|
|
295
|
+
>['value']
|
|
296
|
+
|
|
297
|
+
x.tCh<StandardParsed, ExpectedSubj>()
|
|
298
|
+
x.tCh<ExpectedSubj, StandardParsed>()
|
|
299
|
+
|
|
300
|
+
/* runtime schema check */
|
|
301
|
+
|
|
302
|
+
expect(struct.__schema).toStrictEqual(schema)
|
|
303
|
+
expect(construct.__schema).toStrictEqual(schema)
|
|
304
|
+
expect(construct.__schema === schema).toBe(false)
|
|
305
|
+
|
|
306
|
+
/* parse result check */
|
|
307
|
+
|
|
308
|
+
for (const subj of subjects) {
|
|
309
|
+
const schemaParsed = x.parse(schema, subj)
|
|
310
|
+
|
|
311
|
+
expect(schemaParsed.error).toBe(undefined)
|
|
312
|
+
expect(schemaParsed.data).toStrictEqual(subj)
|
|
313
|
+
|
|
314
|
+
const constructParsed = construct.parse(subj)
|
|
315
|
+
|
|
316
|
+
expect(constructParsed.error).toBe(undefined)
|
|
317
|
+
expect(constructParsed.data).toStrictEqual(subj)
|
|
318
|
+
|
|
319
|
+
const structParsed = struct.parse(subj)
|
|
320
|
+
|
|
321
|
+
expect(structParsed.error).toBe(undefined)
|
|
322
|
+
expect(structParsed.data).toStrictEqual(subj)
|
|
323
|
+
|
|
324
|
+
const standardParsed = struct['~standard'].validate(subj)
|
|
325
|
+
|
|
326
|
+
if (standardParsed instanceof Promise) {
|
|
327
|
+
throw Error('Not expected')
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (standardParsed.issues !== undefined) {
|
|
331
|
+
throw Error('not expected')
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
expect(standardParsed.value).toStrictEqual(subj)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
it('nullable', () => {
|
|
340
|
+
const schema = {
|
|
341
|
+
type: 'record',
|
|
342
|
+
of: { type: 'boolean' },
|
|
343
|
+
nullable: true,
|
|
344
|
+
} as const satisfies x.Schema
|
|
345
|
+
|
|
346
|
+
const struct = x.record(x.boolean()).nullable()
|
|
347
|
+
|
|
348
|
+
type ExpectedSubj = Record<string, boolean> | null
|
|
349
|
+
|
|
350
|
+
const subjects: Array<ExpectedSubj> = [{ x: true }, null]
|
|
351
|
+
|
|
352
|
+
foldA: {
|
|
353
|
+
const construct = x.makeStruct(schema)
|
|
354
|
+
|
|
355
|
+
/* ensure that schema/construct/struct/~standard subject types are identical */
|
|
356
|
+
|
|
357
|
+
type ConstructSchemaSubj = x.Infer<typeof construct.__schema>
|
|
358
|
+
|
|
359
|
+
x.tCh<ConstructSchemaSubj, ExpectedSubj>()
|
|
360
|
+
x.tCh<ExpectedSubj, ConstructSchemaSubj>()
|
|
361
|
+
|
|
362
|
+
type SchemaSubj = x.Infer<typeof schema>
|
|
363
|
+
|
|
364
|
+
x.tCh<SchemaSubj, ExpectedSubj>()
|
|
365
|
+
x.tCh<ExpectedSubj, SchemaSubj>()
|
|
366
|
+
|
|
367
|
+
type StructSubj = x.Infer<typeof struct.__schema>
|
|
368
|
+
|
|
369
|
+
x.tCh<StructSubj, ExpectedSubj>()
|
|
370
|
+
x.tCh<ExpectedSubj, StructSubj>()
|
|
371
|
+
|
|
372
|
+
type StandardSubj = NonNullable<
|
|
373
|
+
(typeof struct)['~standard']['types']
|
|
374
|
+
>['output']
|
|
375
|
+
|
|
376
|
+
x.tCh<StandardSubj, ExpectedSubj>()
|
|
377
|
+
x.tCh<ExpectedSubj, StandardSubj>()
|
|
378
|
+
|
|
379
|
+
/* parsed either type check */
|
|
380
|
+
|
|
381
|
+
type ExpectedParsed = x.ParseResult<ExpectedSubj>
|
|
382
|
+
|
|
383
|
+
const parsed = x.parse(schema, undefined)
|
|
384
|
+
|
|
385
|
+
type SchemaParsed = typeof parsed
|
|
386
|
+
|
|
387
|
+
x.tCh<SchemaParsed, ExpectedParsed>()
|
|
388
|
+
x.tCh<ExpectedParsed, SchemaParsed>()
|
|
389
|
+
|
|
390
|
+
type ConstructParsed = ReturnType<typeof construct.parse>
|
|
391
|
+
|
|
392
|
+
x.tCh<ConstructParsed, ExpectedParsed>()
|
|
393
|
+
x.tCh<ExpectedParsed, ConstructParsed>()
|
|
394
|
+
|
|
395
|
+
type StructParsed = ReturnType<typeof struct.parse>
|
|
396
|
+
|
|
397
|
+
x.tCh<StructParsed, ExpectedParsed>()
|
|
398
|
+
x.tCh<ExpectedParsed, StructParsed>()
|
|
399
|
+
|
|
400
|
+
type StandardParsed = Extract<
|
|
401
|
+
ReturnType<(typeof struct)['~standard']['validate']>,
|
|
402
|
+
{ value: unknown }
|
|
403
|
+
>['value']
|
|
404
|
+
|
|
405
|
+
x.tCh<StandardParsed, ExpectedSubj>()
|
|
406
|
+
x.tCh<ExpectedSubj, StandardParsed>()
|
|
407
|
+
|
|
408
|
+
/* runtime schema check */
|
|
409
|
+
|
|
410
|
+
expect(struct.__schema).toStrictEqual(schema)
|
|
411
|
+
expect(construct.__schema).toStrictEqual(schema)
|
|
412
|
+
expect(construct.__schema === schema).toBe(false)
|
|
413
|
+
|
|
414
|
+
/* parse result check */
|
|
415
|
+
|
|
416
|
+
for (const subj of subjects) {
|
|
417
|
+
const schemaParsed = x.parse(schema, subj)
|
|
418
|
+
|
|
419
|
+
expect(schemaParsed.error).toBe(undefined)
|
|
420
|
+
expect(schemaParsed.data).toStrictEqual(subj)
|
|
421
|
+
|
|
422
|
+
const constructParsed = construct.parse(subj)
|
|
423
|
+
|
|
424
|
+
expect(constructParsed.error).toBe(undefined)
|
|
425
|
+
expect(constructParsed.data).toStrictEqual(subj)
|
|
426
|
+
|
|
427
|
+
const structParsed = struct.parse(subj)
|
|
428
|
+
|
|
429
|
+
expect(structParsed.error).toBe(undefined)
|
|
430
|
+
expect(structParsed.data).toStrictEqual(subj)
|
|
431
|
+
|
|
432
|
+
const standardParsed = struct['~standard'].validate(subj)
|
|
433
|
+
|
|
434
|
+
if (standardParsed instanceof Promise) {
|
|
435
|
+
throw Error('Not expected')
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (standardParsed.issues !== undefined) {
|
|
439
|
+
throw Error('not expected')
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
expect(standardParsed.value).toStrictEqual(subj)
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
it('minLength', () => {
|
|
448
|
+
const schema = {
|
|
449
|
+
type: 'record',
|
|
450
|
+
of: { type: 'boolean' },
|
|
451
|
+
minLength: 2,
|
|
452
|
+
} as const satisfies x.Schema
|
|
453
|
+
|
|
454
|
+
const struct = x.record(x.boolean()).minLength(schema.minLength)
|
|
455
|
+
|
|
456
|
+
type ExpectedSubj = Record<string, boolean>
|
|
457
|
+
|
|
458
|
+
const subjects: Array<ExpectedSubj> = [
|
|
459
|
+
{ 0: true, 1: false },
|
|
460
|
+
{ 0: true, 1: false, 3: true },
|
|
461
|
+
]
|
|
462
|
+
|
|
463
|
+
foldA: {
|
|
464
|
+
const construct = x.makeStruct(schema)
|
|
465
|
+
|
|
466
|
+
/* ensure that schema/construct/struct/~standard subject types are identical */
|
|
467
|
+
|
|
468
|
+
type ConstructSchemaSubj = x.Infer<typeof construct.__schema>
|
|
469
|
+
|
|
470
|
+
x.tCh<ConstructSchemaSubj, ExpectedSubj>()
|
|
471
|
+
x.tCh<ExpectedSubj, ConstructSchemaSubj>()
|
|
472
|
+
|
|
473
|
+
type SchemaSubj = x.Infer<typeof schema>
|
|
474
|
+
|
|
475
|
+
x.tCh<SchemaSubj, ExpectedSubj>()
|
|
476
|
+
x.tCh<ExpectedSubj, SchemaSubj>()
|
|
477
|
+
|
|
478
|
+
type StructSubj = x.Infer<typeof struct.__schema>
|
|
479
|
+
|
|
480
|
+
x.tCh<StructSubj, ExpectedSubj>()
|
|
481
|
+
x.tCh<ExpectedSubj, StructSubj>()
|
|
482
|
+
|
|
483
|
+
type StandardSubj = NonNullable<
|
|
484
|
+
(typeof struct)['~standard']['types']
|
|
485
|
+
>['output']
|
|
486
|
+
|
|
487
|
+
x.tCh<StandardSubj, ExpectedSubj>()
|
|
488
|
+
x.tCh<ExpectedSubj, StandardSubj>()
|
|
489
|
+
|
|
490
|
+
/* parsed either type check */
|
|
491
|
+
|
|
492
|
+
type ExpectedParsed = x.ParseResult<ExpectedSubj>
|
|
493
|
+
|
|
494
|
+
const parsed = x.parse(schema, undefined)
|
|
495
|
+
|
|
496
|
+
type SchemaParsed = typeof parsed
|
|
497
|
+
|
|
498
|
+
x.tCh<SchemaParsed, ExpectedParsed>()
|
|
499
|
+
x.tCh<ExpectedParsed, SchemaParsed>()
|
|
500
|
+
|
|
501
|
+
type ConstructParsed = ReturnType<typeof construct.parse>
|
|
502
|
+
|
|
503
|
+
x.tCh<ConstructParsed, ExpectedParsed>()
|
|
504
|
+
x.tCh<ExpectedParsed, ConstructParsed>()
|
|
505
|
+
|
|
506
|
+
type StructParsed = ReturnType<typeof struct.parse>
|
|
507
|
+
|
|
508
|
+
x.tCh<StructParsed, ExpectedParsed>()
|
|
509
|
+
x.tCh<ExpectedParsed, StructParsed>()
|
|
510
|
+
|
|
511
|
+
type StandardParsed = Extract<
|
|
512
|
+
ReturnType<(typeof struct)['~standard']['validate']>,
|
|
513
|
+
{ value: unknown }
|
|
514
|
+
>['value']
|
|
515
|
+
|
|
516
|
+
x.tCh<StandardParsed, ExpectedSubj>()
|
|
517
|
+
x.tCh<ExpectedSubj, StandardParsed>()
|
|
518
|
+
|
|
519
|
+
/* runtime schema check */
|
|
520
|
+
|
|
521
|
+
expect(struct.__schema).toStrictEqual(schema)
|
|
522
|
+
expect(construct.__schema).toStrictEqual(schema)
|
|
523
|
+
expect(construct.__schema === schema).toBe(false)
|
|
524
|
+
|
|
525
|
+
/* parse result check */
|
|
526
|
+
|
|
527
|
+
for (const subj of subjects) {
|
|
528
|
+
const schemaParsed = x.parse(schema, subj)
|
|
529
|
+
|
|
530
|
+
expect(schemaParsed.error).toBe(undefined)
|
|
531
|
+
expect(schemaParsed.data).toStrictEqual(subj)
|
|
532
|
+
|
|
533
|
+
const constructParsed = construct.parse(subj)
|
|
534
|
+
|
|
535
|
+
expect(constructParsed.error).toBe(undefined)
|
|
536
|
+
expect(constructParsed.data).toStrictEqual(subj)
|
|
537
|
+
|
|
538
|
+
const structParsed = struct.parse(subj)
|
|
539
|
+
|
|
540
|
+
expect(structParsed.error).toBe(undefined)
|
|
541
|
+
expect(structParsed.data).toStrictEqual(subj)
|
|
542
|
+
|
|
543
|
+
const standardParsed = struct['~standard'].validate(subj)
|
|
544
|
+
|
|
545
|
+
if (standardParsed instanceof Promise) {
|
|
546
|
+
throw Error('Not expected')
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (standardParsed.issues !== undefined) {
|
|
550
|
+
throw Error('not expected')
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
expect(standardParsed.value).toStrictEqual(subj)
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
it('maxLength', () => {
|
|
559
|
+
const schema = {
|
|
560
|
+
type: 'record',
|
|
561
|
+
of: { type: 'boolean' },
|
|
562
|
+
maxLength: 3,
|
|
563
|
+
} as const satisfies x.Schema
|
|
564
|
+
|
|
565
|
+
const struct = x.record(x.boolean()).maxLength(schema.maxLength)
|
|
566
|
+
|
|
567
|
+
type ExpectedSubj = Record<string, boolean>
|
|
568
|
+
|
|
569
|
+
const subjects: Array<ExpectedSubj> = [
|
|
570
|
+
{},
|
|
571
|
+
{ 0: true },
|
|
572
|
+
{ 0: true, 1: false },
|
|
573
|
+
{ 0: true, 1: false, 2: true },
|
|
574
|
+
]
|
|
575
|
+
|
|
576
|
+
foldA: {
|
|
577
|
+
const construct = x.makeStruct(schema)
|
|
578
|
+
|
|
579
|
+
/* ensure that schema/construct/struct/~standard subject types are identical */
|
|
580
|
+
|
|
581
|
+
type ConstructSchemaSubj = x.Infer<typeof construct.__schema>
|
|
582
|
+
|
|
583
|
+
x.tCh<ConstructSchemaSubj, ExpectedSubj>()
|
|
584
|
+
x.tCh<ExpectedSubj, ConstructSchemaSubj>()
|
|
585
|
+
|
|
586
|
+
type SchemaSubj = x.Infer<typeof schema>
|
|
587
|
+
|
|
588
|
+
x.tCh<SchemaSubj, ExpectedSubj>()
|
|
589
|
+
x.tCh<ExpectedSubj, SchemaSubj>()
|
|
590
|
+
|
|
591
|
+
type StructSubj = x.Infer<typeof struct.__schema>
|
|
592
|
+
|
|
593
|
+
x.tCh<StructSubj, ExpectedSubj>()
|
|
594
|
+
x.tCh<ExpectedSubj, StructSubj>()
|
|
595
|
+
|
|
596
|
+
type StandardSubj = NonNullable<
|
|
597
|
+
(typeof struct)['~standard']['types']
|
|
598
|
+
>['output']
|
|
599
|
+
|
|
600
|
+
x.tCh<StandardSubj, ExpectedSubj>()
|
|
601
|
+
x.tCh<ExpectedSubj, StandardSubj>()
|
|
602
|
+
|
|
603
|
+
/* parsed either type check */
|
|
604
|
+
|
|
605
|
+
type ExpectedParsed = x.ParseResult<ExpectedSubj>
|
|
606
|
+
|
|
607
|
+
const parsed = x.parse(schema, undefined)
|
|
608
|
+
|
|
609
|
+
type SchemaParsed = typeof parsed
|
|
610
|
+
|
|
611
|
+
x.tCh<SchemaParsed, ExpectedParsed>()
|
|
612
|
+
x.tCh<ExpectedParsed, SchemaParsed>()
|
|
613
|
+
|
|
614
|
+
type ConstructParsed = ReturnType<typeof construct.parse>
|
|
615
|
+
|
|
616
|
+
x.tCh<ConstructParsed, ExpectedParsed>()
|
|
617
|
+
x.tCh<ExpectedParsed, ConstructParsed>()
|
|
618
|
+
|
|
619
|
+
type StructParsed = ReturnType<typeof struct.parse>
|
|
620
|
+
|
|
621
|
+
x.tCh<StructParsed, ExpectedParsed>()
|
|
622
|
+
x.tCh<ExpectedParsed, StructParsed>()
|
|
623
|
+
|
|
624
|
+
type StandardParsed = Extract<
|
|
625
|
+
ReturnType<(typeof struct)['~standard']['validate']>,
|
|
626
|
+
{ value: unknown }
|
|
627
|
+
>['value']
|
|
628
|
+
|
|
629
|
+
x.tCh<StandardParsed, ExpectedSubj>()
|
|
630
|
+
x.tCh<ExpectedSubj, StandardParsed>()
|
|
631
|
+
|
|
632
|
+
/* runtime schema check */
|
|
633
|
+
|
|
634
|
+
expect(struct.__schema).toStrictEqual(schema)
|
|
635
|
+
expect(construct.__schema).toStrictEqual(schema)
|
|
636
|
+
expect(construct.__schema === schema).toBe(false)
|
|
637
|
+
|
|
638
|
+
/* parse result check */
|
|
639
|
+
|
|
640
|
+
for (const subj of subjects) {
|
|
641
|
+
const schemaParsed = x.parse(schema, subj)
|
|
642
|
+
|
|
643
|
+
expect(schemaParsed.error).toBe(undefined)
|
|
644
|
+
expect(schemaParsed.data).toStrictEqual(subj)
|
|
645
|
+
|
|
646
|
+
const constructParsed = construct.parse(subj)
|
|
647
|
+
|
|
648
|
+
expect(constructParsed.error).toBe(undefined)
|
|
649
|
+
expect(constructParsed.data).toStrictEqual(subj)
|
|
650
|
+
|
|
651
|
+
const structParsed = struct.parse(subj)
|
|
652
|
+
|
|
653
|
+
expect(structParsed.error).toBe(undefined)
|
|
654
|
+
expect(structParsed.data).toStrictEqual(subj)
|
|
655
|
+
|
|
656
|
+
const standardParsed = struct['~standard'].validate(subj)
|
|
657
|
+
|
|
658
|
+
if (standardParsed instanceof Promise) {
|
|
659
|
+
throw Error('Not expected')
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (standardParsed.issues !== undefined) {
|
|
663
|
+
throw Error('not expected')
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
expect(standardParsed.value).toStrictEqual(subj)
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
})
|
|
670
|
+
|
|
671
|
+
it('optional + nullable + minLength + maxLength + branded key', () => {
|
|
672
|
+
const schema = {
|
|
673
|
+
type: 'record',
|
|
674
|
+
key: { type: 'string', brand: ['x', 'y'] },
|
|
675
|
+
of: { type: 'boolean' },
|
|
676
|
+
minLength: 1,
|
|
677
|
+
maxLength: 1,
|
|
678
|
+
optional: true,
|
|
679
|
+
nullable: true,
|
|
680
|
+
} as const satisfies x.Schema
|
|
681
|
+
|
|
682
|
+
const struct = x
|
|
683
|
+
.record(x.boolean(), x.string().brand('x', 'y'))
|
|
684
|
+
.minLength(1)
|
|
685
|
+
.maxLength(1)
|
|
686
|
+
.optional()
|
|
687
|
+
.nullable()
|
|
688
|
+
|
|
689
|
+
type ExpectedSubj = Record<string, boolean> | undefined | null
|
|
690
|
+
|
|
691
|
+
const subjects: Array<ExpectedSubj> = [{ x: true }, undefined, null]
|
|
692
|
+
|
|
693
|
+
foldA: {
|
|
694
|
+
const construct = x.makeStruct(schema)
|
|
695
|
+
|
|
696
|
+
/* ensure that schema/construct/struct/~standard subject types are identical */
|
|
697
|
+
|
|
698
|
+
type ConstructSchemaSubj = x.Infer<typeof construct.__schema>
|
|
699
|
+
|
|
700
|
+
x.tCh<ConstructSchemaSubj, ExpectedSubj>()
|
|
701
|
+
x.tCh<ExpectedSubj, ConstructSchemaSubj>()
|
|
702
|
+
|
|
703
|
+
type SchemaSubj = x.Infer<typeof schema>
|
|
704
|
+
|
|
705
|
+
x.tCh<SchemaSubj, ExpectedSubj>()
|
|
706
|
+
x.tCh<ExpectedSubj, SchemaSubj>()
|
|
707
|
+
|
|
708
|
+
type StructSubj = x.Infer<typeof struct.__schema>
|
|
709
|
+
|
|
710
|
+
x.tCh<StructSubj, ExpectedSubj>()
|
|
711
|
+
x.tCh<ExpectedSubj, StructSubj>()
|
|
712
|
+
|
|
713
|
+
type StandardSubj = NonNullable<
|
|
714
|
+
(typeof struct)['~standard']['types']
|
|
715
|
+
>['output']
|
|
716
|
+
|
|
717
|
+
x.tCh<StandardSubj, ExpectedSubj>()
|
|
718
|
+
x.tCh<ExpectedSubj, StandardSubj>()
|
|
719
|
+
|
|
720
|
+
/* parsed either type check */
|
|
721
|
+
|
|
722
|
+
type ExpectedParsed = x.ParseResult<ExpectedSubj>
|
|
723
|
+
|
|
724
|
+
const parsed = x.parse(schema, undefined)
|
|
725
|
+
|
|
726
|
+
type SchemaParsed = typeof parsed
|
|
727
|
+
|
|
728
|
+
x.tCh<SchemaParsed, ExpectedParsed>()
|
|
729
|
+
x.tCh<ExpectedParsed, SchemaParsed>()
|
|
730
|
+
|
|
731
|
+
type ConstructParsed = ReturnType<typeof construct.parse>
|
|
732
|
+
|
|
733
|
+
x.tCh<ConstructParsed, ExpectedParsed>()
|
|
734
|
+
x.tCh<ExpectedParsed, ConstructParsed>()
|
|
735
|
+
|
|
736
|
+
type StructParsed = ReturnType<typeof struct.parse>
|
|
737
|
+
|
|
738
|
+
x.tCh<StructParsed, ExpectedParsed>()
|
|
739
|
+
x.tCh<ExpectedParsed, StructParsed>()
|
|
740
|
+
|
|
741
|
+
type StandardParsed = Extract<
|
|
742
|
+
ReturnType<(typeof struct)['~standard']['validate']>,
|
|
743
|
+
{ value: unknown }
|
|
744
|
+
>['value']
|
|
745
|
+
|
|
746
|
+
x.tCh<StandardParsed, ExpectedSubj>()
|
|
747
|
+
x.tCh<ExpectedSubj, StandardParsed>()
|
|
748
|
+
|
|
749
|
+
/* runtime schema check */
|
|
750
|
+
|
|
751
|
+
expect(struct.__schema).toStrictEqual(schema)
|
|
752
|
+
expect(construct.__schema).toStrictEqual(schema)
|
|
753
|
+
expect(construct.__schema === schema).toBe(false)
|
|
754
|
+
|
|
755
|
+
/* parse result check */
|
|
756
|
+
|
|
757
|
+
for (const subj of subjects) {
|
|
758
|
+
const schemaParsed = x.parse(schema, subj)
|
|
759
|
+
|
|
760
|
+
expect(schemaParsed.error).toBe(undefined)
|
|
761
|
+
expect(schemaParsed.data).toStrictEqual(subj)
|
|
762
|
+
|
|
763
|
+
const constructParsed = construct.parse(subj)
|
|
764
|
+
|
|
765
|
+
expect(constructParsed.error).toBe(undefined)
|
|
766
|
+
expect(constructParsed.data).toStrictEqual(subj)
|
|
767
|
+
|
|
768
|
+
const structParsed = struct.parse(subj)
|
|
769
|
+
|
|
770
|
+
expect(structParsed.error).toBe(undefined)
|
|
771
|
+
expect(structParsed.data).toStrictEqual(subj)
|
|
772
|
+
|
|
773
|
+
const standardParsed = struct['~standard'].validate(subj)
|
|
774
|
+
|
|
775
|
+
if (standardParsed instanceof Promise) {
|
|
776
|
+
throw Error('Not expected')
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (standardParsed.issues !== undefined) {
|
|
780
|
+
throw Error('not expected')
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
expect(standardParsed.value).toStrictEqual(subj)
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
})
|
|
787
|
+
})
|
|
788
|
+
|
|
789
|
+
describe('Struct parameter keys reduction and schema immutability (foldB)', () => {
|
|
790
|
+
it('optional', () => {
|
|
791
|
+
const schema = {
|
|
792
|
+
type: 'record',
|
|
793
|
+
of: { type: 'boolean' },
|
|
794
|
+
optional: true,
|
|
795
|
+
} as const satisfies x.Schema
|
|
796
|
+
|
|
797
|
+
const prevStruct = x.record(x.boolean())
|
|
798
|
+
const struct = prevStruct.optional()
|
|
799
|
+
|
|
800
|
+
type ExpectedKeys =
|
|
801
|
+
| StructSharedKeys
|
|
802
|
+
| 'description'
|
|
803
|
+
| 'nullable'
|
|
804
|
+
| 'minLength'
|
|
805
|
+
| 'maxLength'
|
|
806
|
+
|
|
807
|
+
foldB: {
|
|
808
|
+
const construct = x.makeStruct(schema)
|
|
809
|
+
|
|
810
|
+
/* ensure that struct keys are reduced after application */
|
|
811
|
+
|
|
812
|
+
type StructKeys = keyof typeof struct
|
|
813
|
+
|
|
814
|
+
x.tCh<StructKeys, ExpectedKeys>()
|
|
815
|
+
x.tCh<ExpectedKeys, StructKeys>()
|
|
816
|
+
|
|
817
|
+
type ConstructKeys = keyof typeof construct
|
|
818
|
+
|
|
819
|
+
x.tCh<ConstructKeys, ExpectedKeys>()
|
|
820
|
+
x.tCh<ExpectedKeys, ConstructKeys>()
|
|
821
|
+
|
|
822
|
+
/* ensure that construct/struct schema types are identical */
|
|
823
|
+
|
|
824
|
+
type ExpectedSchema = typeof schema
|
|
825
|
+
type StructSchema = typeof struct.__schema
|
|
826
|
+
|
|
827
|
+
x.tCh<StructSchema, ExpectedSchema>()
|
|
828
|
+
x.tCh<ExpectedSchema, StructSchema>()
|
|
829
|
+
|
|
830
|
+
type ConstructSchema = typeof struct.__schema
|
|
831
|
+
|
|
832
|
+
x.tCh<ConstructSchema, ExpectedSchema>()
|
|
833
|
+
x.tCh<ExpectedSchema, ConstructSchema>()
|
|
834
|
+
|
|
835
|
+
/* runtime schema check */
|
|
836
|
+
|
|
837
|
+
expect(struct.__schema).toStrictEqual(schema)
|
|
838
|
+
expect(construct.__schema).toStrictEqual(schema)
|
|
839
|
+
expect(construct.__schema === schema).toBe(false)
|
|
840
|
+
|
|
841
|
+
/* runtime schema parameter application immutability check */
|
|
842
|
+
|
|
843
|
+
expect(prevStruct.__schema === struct.__schema).toBe(false)
|
|
844
|
+
}
|
|
845
|
+
})
|
|
846
|
+
|
|
847
|
+
it('optional + nullable', () => {
|
|
848
|
+
const schema = {
|
|
849
|
+
type: 'record',
|
|
850
|
+
of: { type: 'boolean' },
|
|
851
|
+
optional: true,
|
|
852
|
+
nullable: true,
|
|
853
|
+
} as const satisfies x.Schema
|
|
854
|
+
|
|
855
|
+
const prevStruct = x.record(x.boolean()).optional()
|
|
856
|
+
const struct = prevStruct.nullable()
|
|
857
|
+
|
|
858
|
+
type ExpectedKeys =
|
|
859
|
+
| StructSharedKeys
|
|
860
|
+
| 'description'
|
|
861
|
+
| 'minLength'
|
|
862
|
+
| 'maxLength'
|
|
863
|
+
|
|
864
|
+
foldB: {
|
|
865
|
+
const construct = x.makeStruct(schema)
|
|
866
|
+
|
|
867
|
+
/* ensure that struct keys are reduced after application */
|
|
868
|
+
|
|
869
|
+
type StructKeys = keyof typeof struct
|
|
870
|
+
|
|
871
|
+
x.tCh<StructKeys, ExpectedKeys>()
|
|
872
|
+
x.tCh<ExpectedKeys, StructKeys>()
|
|
873
|
+
|
|
874
|
+
type ConstructKeys = keyof typeof construct
|
|
875
|
+
|
|
876
|
+
x.tCh<ConstructKeys, ExpectedKeys>()
|
|
877
|
+
x.tCh<ExpectedKeys, ConstructKeys>()
|
|
878
|
+
|
|
879
|
+
/* ensure that construct/struct schema types are identical */
|
|
880
|
+
|
|
881
|
+
type ExpectedSchema = typeof schema
|
|
882
|
+
type StructSchema = typeof struct.__schema
|
|
883
|
+
|
|
884
|
+
x.tCh<StructSchema, ExpectedSchema>()
|
|
885
|
+
x.tCh<ExpectedSchema, StructSchema>()
|
|
886
|
+
|
|
887
|
+
type ConstructSchema = typeof struct.__schema
|
|
888
|
+
|
|
889
|
+
x.tCh<ConstructSchema, ExpectedSchema>()
|
|
890
|
+
x.tCh<ExpectedSchema, ConstructSchema>()
|
|
891
|
+
|
|
892
|
+
/* runtime schema check */
|
|
893
|
+
|
|
894
|
+
expect(struct.__schema).toStrictEqual(schema)
|
|
895
|
+
expect(construct.__schema).toStrictEqual(schema)
|
|
896
|
+
expect(construct.__schema === schema).toBe(false)
|
|
897
|
+
|
|
898
|
+
/* runtime schema parameter application immutability check */
|
|
899
|
+
|
|
900
|
+
expect(prevStruct.__schema === struct.__schema).toBe(false)
|
|
901
|
+
}
|
|
902
|
+
})
|
|
903
|
+
|
|
904
|
+
it('optional + nullable + minLength', () => {
|
|
905
|
+
const schema = {
|
|
906
|
+
type: 'record',
|
|
907
|
+
of: { type: 'boolean' },
|
|
908
|
+
minLength: 1,
|
|
909
|
+
optional: true,
|
|
910
|
+
nullable: true,
|
|
911
|
+
} as const satisfies x.Schema
|
|
912
|
+
|
|
913
|
+
const prevStruct = x.record(x.boolean()).optional().nullable()
|
|
914
|
+
const struct = prevStruct.minLength(1)
|
|
915
|
+
|
|
916
|
+
type ExpectedKeys = StructSharedKeys | 'description' | 'maxLength'
|
|
917
|
+
|
|
918
|
+
foldB: {
|
|
919
|
+
const construct = x.makeStruct(schema)
|
|
920
|
+
|
|
921
|
+
/* ensure that struct keys are reduced after application */
|
|
922
|
+
|
|
923
|
+
type StructKeys = keyof typeof struct
|
|
924
|
+
|
|
925
|
+
x.tCh<StructKeys, ExpectedKeys>()
|
|
926
|
+
x.tCh<ExpectedKeys, StructKeys>()
|
|
927
|
+
|
|
928
|
+
type ConstructKeys = keyof typeof construct
|
|
929
|
+
|
|
930
|
+
x.tCh<ConstructKeys, ExpectedKeys>()
|
|
931
|
+
x.tCh<ExpectedKeys, ConstructKeys>()
|
|
932
|
+
|
|
933
|
+
/* ensure that construct/struct schema types are identical */
|
|
934
|
+
|
|
935
|
+
type ExpectedSchema = typeof schema
|
|
936
|
+
type StructSchema = typeof struct.__schema
|
|
937
|
+
|
|
938
|
+
x.tCh<StructSchema, ExpectedSchema>()
|
|
939
|
+
x.tCh<ExpectedSchema, StructSchema>()
|
|
940
|
+
|
|
941
|
+
type ConstructSchema = typeof struct.__schema
|
|
942
|
+
|
|
943
|
+
x.tCh<ConstructSchema, ExpectedSchema>()
|
|
944
|
+
x.tCh<ExpectedSchema, ConstructSchema>()
|
|
945
|
+
|
|
946
|
+
/* runtime schema check */
|
|
947
|
+
|
|
948
|
+
expect(struct.__schema).toStrictEqual(schema)
|
|
949
|
+
expect(construct.__schema).toStrictEqual(schema)
|
|
950
|
+
expect(construct.__schema === schema).toBe(false)
|
|
951
|
+
|
|
952
|
+
/* runtime schema parameter application immutability check */
|
|
953
|
+
|
|
954
|
+
expect(prevStruct.__schema === struct.__schema).toBe(false)
|
|
955
|
+
}
|
|
956
|
+
})
|
|
957
|
+
|
|
958
|
+
it('optional + nullable + minLength + maxLength', () => {
|
|
959
|
+
const schema = {
|
|
960
|
+
type: 'record',
|
|
961
|
+
of: { type: 'boolean' },
|
|
962
|
+
minLength: 1,
|
|
963
|
+
maxLength: 1,
|
|
964
|
+
optional: true,
|
|
965
|
+
nullable: true,
|
|
966
|
+
} as const satisfies x.Schema
|
|
967
|
+
|
|
968
|
+
const prevStruct = x.record(x.boolean()).optional().nullable().minLength(1)
|
|
969
|
+
|
|
970
|
+
const struct = prevStruct.maxLength(1)
|
|
971
|
+
|
|
972
|
+
type ExpectedKeys = StructSharedKeys | 'description'
|
|
973
|
+
|
|
974
|
+
foldB: {
|
|
975
|
+
const construct = x.makeStruct(schema)
|
|
976
|
+
|
|
977
|
+
/* ensure that struct keys are reduced after application */
|
|
978
|
+
|
|
979
|
+
type StructKeys = keyof typeof struct
|
|
980
|
+
|
|
981
|
+
x.tCh<StructKeys, ExpectedKeys>()
|
|
982
|
+
x.tCh<ExpectedKeys, StructKeys>()
|
|
983
|
+
|
|
984
|
+
type ConstructKeys = keyof typeof construct
|
|
985
|
+
|
|
986
|
+
x.tCh<ConstructKeys, ExpectedKeys>()
|
|
987
|
+
x.tCh<ExpectedKeys, ConstructKeys>()
|
|
988
|
+
|
|
989
|
+
/* ensure that construct/struct schema types are identical */
|
|
990
|
+
|
|
991
|
+
type ExpectedSchema = typeof schema
|
|
992
|
+
type StructSchema = typeof struct.__schema
|
|
993
|
+
|
|
994
|
+
x.tCh<StructSchema, ExpectedSchema>()
|
|
995
|
+
x.tCh<ExpectedSchema, StructSchema>()
|
|
996
|
+
|
|
997
|
+
type ConstructSchema = typeof struct.__schema
|
|
998
|
+
|
|
999
|
+
x.tCh<ConstructSchema, ExpectedSchema>()
|
|
1000
|
+
x.tCh<ExpectedSchema, ConstructSchema>()
|
|
1001
|
+
|
|
1002
|
+
/* runtime schema check */
|
|
1003
|
+
|
|
1004
|
+
expect(struct.__schema).toStrictEqual(schema)
|
|
1005
|
+
expect(construct.__schema).toStrictEqual(schema)
|
|
1006
|
+
expect(construct.__schema === schema).toBe(false)
|
|
1007
|
+
|
|
1008
|
+
/* runtime schema parameter application immutability check */
|
|
1009
|
+
|
|
1010
|
+
expect(prevStruct.__schema === struct.__schema).toBe(false)
|
|
1011
|
+
}
|
|
1012
|
+
})
|
|
1013
|
+
|
|
1014
|
+
it('optional + nullable + minLength + maxLength + description', () => {
|
|
1015
|
+
const schema = {
|
|
1016
|
+
type: 'record',
|
|
1017
|
+
of: { type: 'boolean' },
|
|
1018
|
+
minLength: 1,
|
|
1019
|
+
maxLength: 1,
|
|
1020
|
+
optional: true,
|
|
1021
|
+
nullable: true,
|
|
1022
|
+
description: 'x',
|
|
1023
|
+
} as const satisfies x.Schema
|
|
1024
|
+
|
|
1025
|
+
const prevStruct = x
|
|
1026
|
+
.record(x.boolean())
|
|
1027
|
+
.optional()
|
|
1028
|
+
.minLength(1)
|
|
1029
|
+
.maxLength(1)
|
|
1030
|
+
.nullable()
|
|
1031
|
+
|
|
1032
|
+
const struct = prevStruct.description('x')
|
|
1033
|
+
|
|
1034
|
+
type ExpectedKeys = StructSharedKeys
|
|
1035
|
+
|
|
1036
|
+
foldB: {
|
|
1037
|
+
const construct = x.makeStruct(schema)
|
|
1038
|
+
|
|
1039
|
+
/* ensure that struct keys are reduced after application */
|
|
1040
|
+
|
|
1041
|
+
type StructKeys = keyof typeof struct
|
|
1042
|
+
|
|
1043
|
+
x.tCh<StructKeys, ExpectedKeys>()
|
|
1044
|
+
x.tCh<ExpectedKeys, StructKeys>()
|
|
1045
|
+
|
|
1046
|
+
type ConstructKeys = keyof typeof construct
|
|
1047
|
+
|
|
1048
|
+
x.tCh<ConstructKeys, ExpectedKeys>()
|
|
1049
|
+
x.tCh<ExpectedKeys, ConstructKeys>()
|
|
1050
|
+
|
|
1051
|
+
/* ensure that construct/struct schema types are identical */
|
|
1052
|
+
|
|
1053
|
+
type ExpectedSchema = typeof schema
|
|
1054
|
+
type StructSchema = typeof struct.__schema
|
|
1055
|
+
|
|
1056
|
+
x.tCh<StructSchema, ExpectedSchema>()
|
|
1057
|
+
x.tCh<ExpectedSchema, StructSchema>()
|
|
1058
|
+
|
|
1059
|
+
type ConstructSchema = typeof struct.__schema
|
|
1060
|
+
|
|
1061
|
+
x.tCh<ConstructSchema, ExpectedSchema>()
|
|
1062
|
+
x.tCh<ExpectedSchema, ConstructSchema>()
|
|
1063
|
+
|
|
1064
|
+
/* runtime schema check */
|
|
1065
|
+
|
|
1066
|
+
expect(struct.__schema).toStrictEqual(schema)
|
|
1067
|
+
expect(construct.__schema).toStrictEqual(schema)
|
|
1068
|
+
expect(construct.__schema === schema).toBe(false)
|
|
1069
|
+
|
|
1070
|
+
/* runtime schema parameter application immutability check */
|
|
1071
|
+
|
|
1072
|
+
expect(prevStruct.__schema === struct.__schema).toBe(false)
|
|
1073
|
+
}
|
|
1074
|
+
})
|
|
1075
|
+
|
|
1076
|
+
it('description + maxLength + minLength + nullable + optional', () => {
|
|
1077
|
+
const schema = {
|
|
1078
|
+
type: 'record',
|
|
1079
|
+
of: { type: 'boolean' },
|
|
1080
|
+
minLength: 1,
|
|
1081
|
+
maxLength: 1,
|
|
1082
|
+
optional: true,
|
|
1083
|
+
nullable: true,
|
|
1084
|
+
description: 'x',
|
|
1085
|
+
} as const satisfies x.Schema
|
|
1086
|
+
|
|
1087
|
+
const prevStruct = x
|
|
1088
|
+
.record(x.boolean())
|
|
1089
|
+
.minLength(1)
|
|
1090
|
+
.maxLength(1)
|
|
1091
|
+
.description(schema.description)
|
|
1092
|
+
.nullable()
|
|
1093
|
+
|
|
1094
|
+
const struct = prevStruct.optional()
|
|
1095
|
+
|
|
1096
|
+
type ExpectedKeys = StructSharedKeys
|
|
1097
|
+
|
|
1098
|
+
foldB: {
|
|
1099
|
+
const construct = x.makeStruct(schema)
|
|
1100
|
+
|
|
1101
|
+
/* ensure that struct keys are reduced after application */
|
|
1102
|
+
|
|
1103
|
+
type StructKeys = keyof typeof struct
|
|
1104
|
+
|
|
1105
|
+
x.tCh<StructKeys, ExpectedKeys>()
|
|
1106
|
+
x.tCh<ExpectedKeys, StructKeys>()
|
|
1107
|
+
|
|
1108
|
+
type ConstructKeys = keyof typeof construct
|
|
1109
|
+
|
|
1110
|
+
x.tCh<ConstructKeys, ExpectedKeys>()
|
|
1111
|
+
x.tCh<ExpectedKeys, ConstructKeys>()
|
|
1112
|
+
|
|
1113
|
+
/* ensure that construct/struct schema types are identical */
|
|
1114
|
+
|
|
1115
|
+
type ExpectedSchema = typeof schema
|
|
1116
|
+
type StructSchema = typeof struct.__schema
|
|
1117
|
+
|
|
1118
|
+
x.tCh<StructSchema, ExpectedSchema>()
|
|
1119
|
+
x.tCh<ExpectedSchema, StructSchema>()
|
|
1120
|
+
|
|
1121
|
+
type ConstructSchema = typeof struct.__schema
|
|
1122
|
+
|
|
1123
|
+
x.tCh<ConstructSchema, ExpectedSchema>()
|
|
1124
|
+
x.tCh<ExpectedSchema, ConstructSchema>()
|
|
1125
|
+
|
|
1126
|
+
/* runtime schema check */
|
|
1127
|
+
|
|
1128
|
+
expect(struct.__schema).toStrictEqual(schema)
|
|
1129
|
+
expect(construct.__schema).toStrictEqual(schema)
|
|
1130
|
+
expect(construct.__schema === schema).toBe(false)
|
|
1131
|
+
|
|
1132
|
+
/* runtime schema parameter application immutability check */
|
|
1133
|
+
|
|
1134
|
+
expect(prevStruct.__schema === struct.__schema).toBe(false)
|
|
1135
|
+
}
|
|
1136
|
+
})
|
|
1137
|
+
})
|
|
1138
|
+
|
|
1139
|
+
describe('ERROR_CODE.invalidType (foldC, foldE)', () => {
|
|
1140
|
+
it('iterate over fixture.DATA_TYPE', () => {
|
|
1141
|
+
const schema = {
|
|
1142
|
+
type: 'record',
|
|
1143
|
+
of: { type: 'boolean' },
|
|
1144
|
+
} as const satisfies x.Schema
|
|
1145
|
+
|
|
1146
|
+
const struct = x.record(x.boolean())
|
|
1147
|
+
const source = fixture.DATA_TYPE.filter(([type]) => type !== 'object')
|
|
1148
|
+
|
|
1149
|
+
foldC: {
|
|
1150
|
+
const construct = x.makeStruct(schema)
|
|
1151
|
+
|
|
1152
|
+
for (const [kind, types] of source) {
|
|
1153
|
+
if (kind === schema.type) {
|
|
1154
|
+
continue
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
for (const subject of types) {
|
|
1158
|
+
const expectedError = [
|
|
1159
|
+
{
|
|
1160
|
+
code: x.ERROR_CODE.invalidType,
|
|
1161
|
+
schema: schema,
|
|
1162
|
+
subject: subject,
|
|
1163
|
+
path: [],
|
|
1164
|
+
},
|
|
1165
|
+
]
|
|
1166
|
+
|
|
1167
|
+
const parsedSchema = x.parse(schema, subject)
|
|
1168
|
+
const parsedConstruct = construct.parse(subject)
|
|
1169
|
+
const parsedStruct = struct.parse(subject)
|
|
1170
|
+
|
|
1171
|
+
expect(parsedSchema.error).toStrictEqual(expectedError)
|
|
1172
|
+
expect(parsedConstruct.error).toStrictEqual(expectedError)
|
|
1173
|
+
expect(parsedStruct.error).toStrictEqual(expectedError)
|
|
1174
|
+
|
|
1175
|
+
const parsedStandard = struct['~standard'].validate(subject)
|
|
1176
|
+
|
|
1177
|
+
if (parsedStandard instanceof Promise) {
|
|
1178
|
+
throw Error('Not expected')
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
expect(parsedStandard.issues).toStrictEqual([
|
|
1182
|
+
{ message: x.ERROR_CODE.invalidType, path: [] },
|
|
1183
|
+
])
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
})
|
|
1188
|
+
|
|
1189
|
+
it('InvalidSubject error of nested schema should have correct path/schema/subject', () => {
|
|
1190
|
+
const schema = {
|
|
1191
|
+
type: 'record',
|
|
1192
|
+
of: {
|
|
1193
|
+
type: 'array',
|
|
1194
|
+
of: {
|
|
1195
|
+
type: 'object',
|
|
1196
|
+
of: {
|
|
1197
|
+
y: { type: 'literal', of: '_' },
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1200
|
+
},
|
|
1201
|
+
} as const satisfies x.Schema
|
|
1202
|
+
|
|
1203
|
+
const struct = x.record(x.array(x.object({ y: x.literal('_') })))
|
|
1204
|
+
|
|
1205
|
+
// prettier-ignore
|
|
1206
|
+
const samples: Array<[
|
|
1207
|
+
subj: Record<string, Array<{ y: unknown }>>,
|
|
1208
|
+
invalidSubj: unknown,
|
|
1209
|
+
invalidSubjSchema: x.Schema,
|
|
1210
|
+
errorPath: x.ErrorPath,
|
|
1211
|
+
]
|
|
1212
|
+
> = [
|
|
1213
|
+
[{ x: [{ y: '+' }, { y: '_' }, { y: '_' }] }, '+', schema.of.of.of.y, ['x', 0, 'y']],
|
|
1214
|
+
[{ y: [{ y: '_' }, { y: '+' }, { y: '_' }] }, '+', schema.of.of.of.y, ['y', 1, 'y']],
|
|
1215
|
+
[{ z: [{ y: '_' }, { y: '_' }, { y: '+' }] }, '+', schema.of.of.of.y, ['z', 2, 'y']],
|
|
1216
|
+
]
|
|
1217
|
+
|
|
1218
|
+
foldE: {
|
|
1219
|
+
const construct = x.makeStruct(schema)
|
|
1220
|
+
|
|
1221
|
+
for (const [subject, invalidSubj, invalidSubjSchema, path] of samples) {
|
|
1222
|
+
const expectedError = [
|
|
1223
|
+
{
|
|
1224
|
+
path,
|
|
1225
|
+
code: x.ERROR_CODE.invalidType,
|
|
1226
|
+
schema: invalidSubjSchema,
|
|
1227
|
+
subject: invalidSubj,
|
|
1228
|
+
},
|
|
1229
|
+
]
|
|
1230
|
+
|
|
1231
|
+
const parsedSchema = x.parse(schema, subject)
|
|
1232
|
+
const parsedConstruct = construct.parse(subject)
|
|
1233
|
+
const parsedStruct = struct.parse(subject)
|
|
1234
|
+
|
|
1235
|
+
expect(parsedSchema.error).toStrictEqual(expectedError)
|
|
1236
|
+
expect(parsedConstruct.error).toStrictEqual(expectedError)
|
|
1237
|
+
expect(parsedStruct.error).toStrictEqual(expectedError)
|
|
1238
|
+
|
|
1239
|
+
const parsedStandard = struct['~standard'].validate(subject)
|
|
1240
|
+
|
|
1241
|
+
if (parsedStandard instanceof Promise) {
|
|
1242
|
+
throw Error('Not expected')
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
expect(parsedStandard.issues).toStrictEqual([
|
|
1246
|
+
{ path, message: x.ERROR_CODE.invalidType },
|
|
1247
|
+
])
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
})
|
|
1251
|
+
|
|
1252
|
+
it('Multiple errors', () => {
|
|
1253
|
+
const schema = {
|
|
1254
|
+
type: 'record',
|
|
1255
|
+
of: { type: 'boolean' },
|
|
1256
|
+
} as const satisfies x.Schema
|
|
1257
|
+
|
|
1258
|
+
const subject = { x: true, y: '', z: 0 }
|
|
1259
|
+
|
|
1260
|
+
const parsed = x.parse(schema, subject)
|
|
1261
|
+
const expectedError: x.InvalidSubject[] = [
|
|
1262
|
+
{
|
|
1263
|
+
code: x.ERROR_CODE.invalidType,
|
|
1264
|
+
path: ['y'],
|
|
1265
|
+
subject: subject.y,
|
|
1266
|
+
schema: schema.of,
|
|
1267
|
+
},
|
|
1268
|
+
{
|
|
1269
|
+
code: x.ERROR_CODE.invalidType,
|
|
1270
|
+
path: ['z'],
|
|
1271
|
+
subject: subject.z,
|
|
1272
|
+
schema: schema.of,
|
|
1273
|
+
},
|
|
1274
|
+
]
|
|
1275
|
+
|
|
1276
|
+
expect(parsed.error).toStrictEqual(expectedError)
|
|
1277
|
+
})
|
|
1278
|
+
})
|
|
1279
|
+
|
|
1280
|
+
describe('ERROR_CODE.invalidRange (foldD)', () => {
|
|
1281
|
+
it('minLength', () => {
|
|
1282
|
+
const schema = {
|
|
1283
|
+
type: 'record',
|
|
1284
|
+
of: { type: 'boolean' },
|
|
1285
|
+
minLength: 3,
|
|
1286
|
+
} as const satisfies x.Schema
|
|
1287
|
+
|
|
1288
|
+
const struct = x.record(x.boolean()).minLength(schema.minLength)
|
|
1289
|
+
const subjects = [
|
|
1290
|
+
{},
|
|
1291
|
+
{ 0: true },
|
|
1292
|
+
{ 0: true, 1: false },
|
|
1293
|
+
{ 0: true, 1: false, 2: undefined },
|
|
1294
|
+
]
|
|
1295
|
+
|
|
1296
|
+
foldD: {
|
|
1297
|
+
const construct = x.makeStruct(schema)
|
|
1298
|
+
|
|
1299
|
+
for (const subject of subjects) {
|
|
1300
|
+
const expectedError = [
|
|
1301
|
+
{
|
|
1302
|
+
code: x.ERROR_CODE.invalidRange,
|
|
1303
|
+
schema: schema,
|
|
1304
|
+
subject: subject,
|
|
1305
|
+
path: [],
|
|
1306
|
+
},
|
|
1307
|
+
]
|
|
1308
|
+
|
|
1309
|
+
const parsedSchema = x.parse(schema, subject)
|
|
1310
|
+
const parsedConstruct = construct.parse(subject)
|
|
1311
|
+
const parsedStruct = struct.parse(subject)
|
|
1312
|
+
|
|
1313
|
+
expect(parsedSchema.error).toStrictEqual(expectedError)
|
|
1314
|
+
expect(parsedConstruct.error).toStrictEqual(expectedError)
|
|
1315
|
+
expect(parsedStruct.error).toStrictEqual(expectedError)
|
|
1316
|
+
|
|
1317
|
+
const parsedStandard = struct['~standard'].validate(subject)
|
|
1318
|
+
|
|
1319
|
+
if (parsedStandard instanceof Promise) {
|
|
1320
|
+
throw Error('Not expected')
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
expect(parsedStandard.issues).toStrictEqual([
|
|
1324
|
+
{ message: x.ERROR_CODE.invalidRange, path: [] },
|
|
1325
|
+
])
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
})
|
|
1329
|
+
|
|
1330
|
+
it('maxLength', () => {
|
|
1331
|
+
const schema = {
|
|
1332
|
+
type: 'record',
|
|
1333
|
+
of: { type: 'boolean' },
|
|
1334
|
+
maxLength: 1,
|
|
1335
|
+
} as const satisfies x.Schema
|
|
1336
|
+
|
|
1337
|
+
const struct = x.record(x.boolean()).maxLength(schema.maxLength)
|
|
1338
|
+
const subjects = [
|
|
1339
|
+
{ 0: false, 1: true },
|
|
1340
|
+
{ 0: false, 1: true, 2: false },
|
|
1341
|
+
{ 0: false, 1: true, 2: false, 3: true },
|
|
1342
|
+
]
|
|
1343
|
+
|
|
1344
|
+
foldD: {
|
|
1345
|
+
const construct = x.makeStruct(schema)
|
|
1346
|
+
|
|
1347
|
+
for (const subject of subjects) {
|
|
1348
|
+
const expectedError = [
|
|
1349
|
+
{
|
|
1350
|
+
code: x.ERROR_CODE.invalidRange,
|
|
1351
|
+
schema: schema,
|
|
1352
|
+
subject: subject,
|
|
1353
|
+
path: [],
|
|
1354
|
+
},
|
|
1355
|
+
]
|
|
1356
|
+
|
|
1357
|
+
const parsedSchema = x.parse(schema, subject)
|
|
1358
|
+
const parsedConstruct = construct.parse(subject)
|
|
1359
|
+
const parsedStruct = struct.parse(subject)
|
|
1360
|
+
|
|
1361
|
+
expect(parsedSchema.error).toStrictEqual(expectedError)
|
|
1362
|
+
expect(parsedConstruct.error).toStrictEqual(expectedError)
|
|
1363
|
+
expect(parsedStruct.error).toStrictEqual(expectedError)
|
|
1364
|
+
|
|
1365
|
+
const parsedStandard = struct['~standard'].validate(subject)
|
|
1366
|
+
|
|
1367
|
+
if (parsedStandard instanceof Promise) {
|
|
1368
|
+
throw Error('Not expected')
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
expect(parsedStandard.issues).toStrictEqual([
|
|
1372
|
+
{ message: x.ERROR_CODE.invalidRange, path: [] },
|
|
1373
|
+
])
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
})
|
|
1377
|
+
|
|
1378
|
+
it('minLength + maxLength', () => {
|
|
1379
|
+
const schema = {
|
|
1380
|
+
type: 'array',
|
|
1381
|
+
of: { type: 'boolean' },
|
|
1382
|
+
maxLength: 2,
|
|
1383
|
+
minLength: 2,
|
|
1384
|
+
} as const satisfies x.Schema
|
|
1385
|
+
|
|
1386
|
+
const struct = x
|
|
1387
|
+
.array(x.boolean())
|
|
1388
|
+
.minLength(schema.minLength)
|
|
1389
|
+
.maxLength(schema.maxLength)
|
|
1390
|
+
|
|
1391
|
+
const subjects = [[], [false], [false, true, false]]
|
|
1392
|
+
|
|
1393
|
+
foldD: {
|
|
1394
|
+
const construct = x.makeStruct(schema)
|
|
1395
|
+
|
|
1396
|
+
for (const subject of subjects) {
|
|
1397
|
+
const expectedError = [
|
|
1398
|
+
{
|
|
1399
|
+
code: x.ERROR_CODE.invalidRange,
|
|
1400
|
+
schema: schema,
|
|
1401
|
+
subject: subject,
|
|
1402
|
+
path: [],
|
|
1403
|
+
},
|
|
1404
|
+
]
|
|
1405
|
+
|
|
1406
|
+
const parsedSchema = x.parse(schema, subject)
|
|
1407
|
+
const parsedConstruct = construct.parse(subject)
|
|
1408
|
+
const parsedStruct = struct.parse(subject)
|
|
1409
|
+
|
|
1410
|
+
expect(parsedSchema.error).toStrictEqual(expectedError)
|
|
1411
|
+
expect(parsedConstruct.error).toStrictEqual(expectedError)
|
|
1412
|
+
expect(parsedStruct.error).toStrictEqual(expectedError)
|
|
1413
|
+
|
|
1414
|
+
const parsedStandard = struct['~standard'].validate(subject)
|
|
1415
|
+
|
|
1416
|
+
if (parsedStandard instanceof Promise) {
|
|
1417
|
+
throw Error('Not expected')
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
expect(parsedStandard.issues).toStrictEqual([
|
|
1421
|
+
{ message: x.ERROR_CODE.invalidRange, path: [] },
|
|
1422
|
+
])
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
})
|
|
1426
|
+
})
|
|
1427
|
+
|
|
1428
|
+
describe('Compound schema specifics (foldA)', () => {
|
|
1429
|
+
it('nested primitive schema: optional + nullable + brand', () => {
|
|
1430
|
+
const schema = {
|
|
1431
|
+
type: 'record',
|
|
1432
|
+
of: {
|
|
1433
|
+
type: 'boolean',
|
|
1434
|
+
optional: true,
|
|
1435
|
+
nullable: true,
|
|
1436
|
+
brand: ['x', 'y'],
|
|
1437
|
+
},
|
|
1438
|
+
} as const satisfies x.Schema
|
|
1439
|
+
|
|
1440
|
+
const struct = x.record(
|
|
1441
|
+
x
|
|
1442
|
+
.boolean()
|
|
1443
|
+
.optional()
|
|
1444
|
+
.nullable()
|
|
1445
|
+
.brand(...schema.of.brand)
|
|
1446
|
+
)
|
|
1447
|
+
|
|
1448
|
+
type Branded = boolean & { __x: 'y' }
|
|
1449
|
+
|
|
1450
|
+
// TODO: expected that `{ x?: Branded | undefined | null }` will not be
|
|
1451
|
+
// equivalent to `Record<Branded | undefined | null>`
|
|
1452
|
+
//
|
|
1453
|
+
// either `tCh` has a flaw or typescript itself, need to investigate this later
|
|
1454
|
+
//
|
|
1455
|
+
type ExpectedSubj = { x?: Branded | undefined | null }
|
|
1456
|
+
|
|
1457
|
+
const subjects = [
|
|
1458
|
+
{},
|
|
1459
|
+
{ x: null },
|
|
1460
|
+
{ x: true as Branded },
|
|
1461
|
+
{ x: false as Branded },
|
|
1462
|
+
] as const satisfies Array<ExpectedSubj>
|
|
1463
|
+
|
|
1464
|
+
foldA: {
|
|
1465
|
+
const construct = x.makeStruct(schema)
|
|
1466
|
+
|
|
1467
|
+
/* ensure that schema/construct/struct/~standard subject types are identical */
|
|
1468
|
+
|
|
1469
|
+
type ConstructSchemaSubj = x.Infer<typeof construct.__schema>
|
|
1470
|
+
|
|
1471
|
+
x.tCh<ConstructSchemaSubj, ExpectedSubj>()
|
|
1472
|
+
x.tCh<ExpectedSubj, ConstructSchemaSubj>()
|
|
1473
|
+
|
|
1474
|
+
type SchemaSubj = x.Infer<typeof schema>
|
|
1475
|
+
|
|
1476
|
+
x.tCh<SchemaSubj, ExpectedSubj>()
|
|
1477
|
+
x.tCh<ExpectedSubj, SchemaSubj>()
|
|
1478
|
+
|
|
1479
|
+
type StructSubj = x.Infer<typeof struct.__schema>
|
|
1480
|
+
|
|
1481
|
+
x.tCh<StructSubj, ExpectedSubj>()
|
|
1482
|
+
x.tCh<ExpectedSubj, StructSubj>()
|
|
1483
|
+
|
|
1484
|
+
type StandardSubj = NonNullable<
|
|
1485
|
+
(typeof struct)['~standard']['types']
|
|
1486
|
+
>['output']
|
|
1487
|
+
|
|
1488
|
+
x.tCh<StandardSubj, ExpectedSubj>()
|
|
1489
|
+
x.tCh<ExpectedSubj, StandardSubj>()
|
|
1490
|
+
|
|
1491
|
+
/* parsed either type check */
|
|
1492
|
+
|
|
1493
|
+
type ExpectedParsed = x.ParseResult<ExpectedSubj>
|
|
1494
|
+
|
|
1495
|
+
const parsed = x.parse(schema, undefined)
|
|
1496
|
+
|
|
1497
|
+
type SchemaParsed = typeof parsed
|
|
1498
|
+
|
|
1499
|
+
x.tCh<SchemaParsed, ExpectedParsed>()
|
|
1500
|
+
x.tCh<ExpectedParsed, SchemaParsed>()
|
|
1501
|
+
|
|
1502
|
+
type ConstructParsed = ReturnType<typeof construct.parse>
|
|
1503
|
+
|
|
1504
|
+
x.tCh<ConstructParsed, ExpectedParsed>()
|
|
1505
|
+
x.tCh<ExpectedParsed, ConstructParsed>()
|
|
1506
|
+
|
|
1507
|
+
type StructParsed = ReturnType<typeof struct.parse>
|
|
1508
|
+
|
|
1509
|
+
x.tCh<StructParsed, ExpectedParsed>()
|
|
1510
|
+
x.tCh<ExpectedParsed, StructParsed>()
|
|
1511
|
+
|
|
1512
|
+
type StandardParsed = Extract<
|
|
1513
|
+
ReturnType<(typeof struct)['~standard']['validate']>,
|
|
1514
|
+
{ value: unknown }
|
|
1515
|
+
>['value']
|
|
1516
|
+
|
|
1517
|
+
x.tCh<StandardParsed, ExpectedSubj>()
|
|
1518
|
+
x.tCh<ExpectedSubj, StandardParsed>()
|
|
1519
|
+
|
|
1520
|
+
/* runtime schema check */
|
|
1521
|
+
|
|
1522
|
+
expect(struct.__schema).toStrictEqual(schema)
|
|
1523
|
+
expect(construct.__schema).toStrictEqual(schema)
|
|
1524
|
+
expect(construct.__schema === schema).toBe(false)
|
|
1525
|
+
|
|
1526
|
+
/* parse result check */
|
|
1527
|
+
|
|
1528
|
+
for (const subj of subjects) {
|
|
1529
|
+
const schemaParsed = x.parse(schema, subj)
|
|
1530
|
+
|
|
1531
|
+
expect(schemaParsed.error).toBe(undefined)
|
|
1532
|
+
expect(schemaParsed.data).toStrictEqual(subj)
|
|
1533
|
+
|
|
1534
|
+
const constructParsed = construct.parse(subj)
|
|
1535
|
+
|
|
1536
|
+
expect(constructParsed.error).toBe(undefined)
|
|
1537
|
+
expect(constructParsed.data).toStrictEqual(subj)
|
|
1538
|
+
|
|
1539
|
+
const structParsed = struct.parse(subj)
|
|
1540
|
+
|
|
1541
|
+
expect(structParsed.error).toBe(undefined)
|
|
1542
|
+
expect(structParsed.data).toStrictEqual(subj)
|
|
1543
|
+
|
|
1544
|
+
const standardParsed = struct['~standard'].validate(subj)
|
|
1545
|
+
|
|
1546
|
+
if (standardParsed instanceof Promise) {
|
|
1547
|
+
throw Error('Not expected')
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
if (standardParsed.issues !== undefined) {
|
|
1551
|
+
throw Error('not expected')
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
expect(standardParsed.value).toStrictEqual(subj)
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
})
|
|
1558
|
+
|
|
1559
|
+
/**
|
|
1560
|
+
* We ensure that when a developer makes `Object.keys` on a record,
|
|
1561
|
+
* they are, in fact, keys of existing entities within its record.
|
|
1562
|
+
**/
|
|
1563
|
+
it('parsed record should not keep if its value is undefined', () => {
|
|
1564
|
+
const struct = x.record(x.boolean().optional())
|
|
1565
|
+
const parsed = struct.parse({ x: undefined })
|
|
1566
|
+
|
|
1567
|
+
expect(parsed.data).toStrictEqual({})
|
|
1568
|
+
})
|
|
1569
|
+
|
|
1570
|
+
it('nested compound schema: optional + nullable', () => {
|
|
1571
|
+
const schema = {
|
|
1572
|
+
type: 'record',
|
|
1573
|
+
of: {
|
|
1574
|
+
type: 'array',
|
|
1575
|
+
of: { type: 'boolean' },
|
|
1576
|
+
optional: true,
|
|
1577
|
+
nullable: true,
|
|
1578
|
+
},
|
|
1579
|
+
} as const satisfies x.Schema
|
|
1580
|
+
|
|
1581
|
+
const struct = x.record(x.array(x.boolean()).optional().nullable())
|
|
1582
|
+
|
|
1583
|
+
type ExpectedSubj = { x?: Array<boolean> | undefined | null }
|
|
1584
|
+
|
|
1585
|
+
const subjects = [
|
|
1586
|
+
{ x: [] },
|
|
1587
|
+
{ x: null },
|
|
1588
|
+
{ x: [] },
|
|
1589
|
+
{ x: [true] },
|
|
1590
|
+
] as const satisfies Array<ExpectedSubj>
|
|
1591
|
+
|
|
1592
|
+
foldA: {
|
|
1593
|
+
const construct = x.makeStruct(schema)
|
|
1594
|
+
|
|
1595
|
+
/* ensure that schema/construct/struct/~standard subject types are identical */
|
|
1596
|
+
|
|
1597
|
+
type ConstructSchemaSubj = x.Infer<typeof construct.__schema>
|
|
1598
|
+
|
|
1599
|
+
x.tCh<ConstructSchemaSubj, ExpectedSubj>()
|
|
1600
|
+
x.tCh<ExpectedSubj, ConstructSchemaSubj>()
|
|
1601
|
+
|
|
1602
|
+
type SchemaSubj = x.Infer<typeof schema>
|
|
1603
|
+
|
|
1604
|
+
x.tCh<SchemaSubj, ExpectedSubj>()
|
|
1605
|
+
x.tCh<ExpectedSubj, SchemaSubj>()
|
|
1606
|
+
|
|
1607
|
+
type StructSubj = x.Infer<typeof struct.__schema>
|
|
1608
|
+
|
|
1609
|
+
x.tCh<StructSubj, ExpectedSubj>()
|
|
1610
|
+
x.tCh<ExpectedSubj, StructSubj>()
|
|
1611
|
+
|
|
1612
|
+
type StandardSubj = NonNullable<
|
|
1613
|
+
(typeof struct)['~standard']['types']
|
|
1614
|
+
>['output']
|
|
1615
|
+
|
|
1616
|
+
x.tCh<StandardSubj, ExpectedSubj>()
|
|
1617
|
+
x.tCh<ExpectedSubj, StandardSubj>()
|
|
1618
|
+
|
|
1619
|
+
/* parsed either type check */
|
|
1620
|
+
|
|
1621
|
+
type ExpectedParsed = x.ParseResult<ExpectedSubj>
|
|
1622
|
+
|
|
1623
|
+
const parsed = x.parse(schema, undefined)
|
|
1624
|
+
|
|
1625
|
+
type SchemaParsed = typeof parsed
|
|
1626
|
+
|
|
1627
|
+
x.tCh<SchemaParsed, ExpectedParsed>()
|
|
1628
|
+
x.tCh<ExpectedParsed, SchemaParsed>()
|
|
1629
|
+
|
|
1630
|
+
type ConstructParsed = ReturnType<typeof construct.parse>
|
|
1631
|
+
|
|
1632
|
+
x.tCh<ConstructParsed, ExpectedParsed>()
|
|
1633
|
+
x.tCh<ExpectedParsed, ConstructParsed>()
|
|
1634
|
+
|
|
1635
|
+
type StructParsed = ReturnType<typeof struct.parse>
|
|
1636
|
+
|
|
1637
|
+
x.tCh<StructParsed, ExpectedParsed>()
|
|
1638
|
+
x.tCh<ExpectedParsed, StructParsed>()
|
|
1639
|
+
|
|
1640
|
+
type StandardParsed = Extract<
|
|
1641
|
+
ReturnType<(typeof struct)['~standard']['validate']>,
|
|
1642
|
+
{ value: unknown }
|
|
1643
|
+
>['value']
|
|
1644
|
+
|
|
1645
|
+
x.tCh<StandardParsed, ExpectedSubj>()
|
|
1646
|
+
x.tCh<ExpectedSubj, StandardParsed>()
|
|
1647
|
+
|
|
1648
|
+
/* runtime schema check */
|
|
1649
|
+
|
|
1650
|
+
expect(struct.__schema).toStrictEqual(schema)
|
|
1651
|
+
expect(construct.__schema).toStrictEqual(schema)
|
|
1652
|
+
expect(construct.__schema === schema).toBe(false)
|
|
1653
|
+
|
|
1654
|
+
/* parse result check */
|
|
1655
|
+
|
|
1656
|
+
for (const subj of subjects) {
|
|
1657
|
+
const schemaParsed = x.parse(schema, subj)
|
|
1658
|
+
|
|
1659
|
+
expect(schemaParsed.error).toBe(undefined)
|
|
1660
|
+
expect(schemaParsed.data).toStrictEqual(subj)
|
|
1661
|
+
|
|
1662
|
+
const constructParsed = construct.parse(subj)
|
|
1663
|
+
|
|
1664
|
+
expect(constructParsed.error).toBe(undefined)
|
|
1665
|
+
expect(constructParsed.data).toStrictEqual(subj)
|
|
1666
|
+
|
|
1667
|
+
const structParsed = struct.parse(subj)
|
|
1668
|
+
|
|
1669
|
+
expect(structParsed.error).toBe(undefined)
|
|
1670
|
+
expect(structParsed.data).toStrictEqual(subj)
|
|
1671
|
+
|
|
1672
|
+
const standardParsed = struct['~standard'].validate(subj)
|
|
1673
|
+
|
|
1674
|
+
if (standardParsed instanceof Promise) {
|
|
1675
|
+
throw Error('Not expected')
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
if (standardParsed.issues !== undefined) {
|
|
1679
|
+
throw Error('not expected')
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
expect(standardParsed.value).toStrictEqual(subj)
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
})
|
|
1686
|
+
|
|
1687
|
+
it('nested by itself (schema depth: 4)', () => {
|
|
1688
|
+
const schema = {
|
|
1689
|
+
type: 'record',
|
|
1690
|
+
of: {
|
|
1691
|
+
type: 'record',
|
|
1692
|
+
of: {
|
|
1693
|
+
type: 'record',
|
|
1694
|
+
of: { type: 'boolean' },
|
|
1695
|
+
},
|
|
1696
|
+
},
|
|
1697
|
+
} as const satisfies x.Schema
|
|
1698
|
+
|
|
1699
|
+
const struct = x.record(x.record(x.record(x.boolean())))
|
|
1700
|
+
|
|
1701
|
+
type ExpectedSubj = Record<string, Record<string, Record<string, boolean>>>
|
|
1702
|
+
|
|
1703
|
+
const subjects = [
|
|
1704
|
+
{ x: { y: { z: true } } },
|
|
1705
|
+
{ x: { y: { z: false } } },
|
|
1706
|
+
] as const satisfies Array<ExpectedSubj>
|
|
1707
|
+
|
|
1708
|
+
foldA: {
|
|
1709
|
+
const construct = x.makeStruct(schema)
|
|
1710
|
+
|
|
1711
|
+
/* ensure that schema/construct/struct/~standard subject types are identical */
|
|
1712
|
+
|
|
1713
|
+
type ConstructSchemaSubj = x.Infer<typeof construct.__schema>
|
|
1714
|
+
|
|
1715
|
+
x.tCh<ConstructSchemaSubj, ExpectedSubj>()
|
|
1716
|
+
x.tCh<ExpectedSubj, ConstructSchemaSubj>()
|
|
1717
|
+
|
|
1718
|
+
type SchemaSubj = x.Infer<typeof schema>
|
|
1719
|
+
|
|
1720
|
+
x.tCh<SchemaSubj, ExpectedSubj>()
|
|
1721
|
+
x.tCh<ExpectedSubj, SchemaSubj>()
|
|
1722
|
+
|
|
1723
|
+
type StructSubj = x.Infer<typeof struct.__schema>
|
|
1724
|
+
|
|
1725
|
+
x.tCh<StructSubj, ExpectedSubj>()
|
|
1726
|
+
x.tCh<ExpectedSubj, StructSubj>()
|
|
1727
|
+
|
|
1728
|
+
type StandardSubj = NonNullable<
|
|
1729
|
+
(typeof struct)['~standard']['types']
|
|
1730
|
+
>['output']
|
|
1731
|
+
|
|
1732
|
+
x.tCh<StandardSubj, ExpectedSubj>()
|
|
1733
|
+
x.tCh<ExpectedSubj, StandardSubj>()
|
|
1734
|
+
|
|
1735
|
+
/* parsed either type check */
|
|
1736
|
+
|
|
1737
|
+
type ExpectedParsed = x.ParseResult<ExpectedSubj>
|
|
1738
|
+
|
|
1739
|
+
const parsed = x.parse(schema, undefined)
|
|
1740
|
+
|
|
1741
|
+
type SchemaParsed = typeof parsed
|
|
1742
|
+
|
|
1743
|
+
x.tCh<SchemaParsed, ExpectedParsed>()
|
|
1744
|
+
x.tCh<ExpectedParsed, SchemaParsed>()
|
|
1745
|
+
|
|
1746
|
+
type ConstructParsed = ReturnType<typeof construct.parse>
|
|
1747
|
+
|
|
1748
|
+
x.tCh<ConstructParsed, ExpectedParsed>()
|
|
1749
|
+
x.tCh<ExpectedParsed, ConstructParsed>()
|
|
1750
|
+
|
|
1751
|
+
type StructParsed = ReturnType<typeof struct.parse>
|
|
1752
|
+
|
|
1753
|
+
x.tCh<StructParsed, ExpectedParsed>()
|
|
1754
|
+
x.tCh<ExpectedParsed, StructParsed>()
|
|
1755
|
+
|
|
1756
|
+
type StandardParsed = Extract<
|
|
1757
|
+
ReturnType<(typeof struct)['~standard']['validate']>,
|
|
1758
|
+
{ value: unknown }
|
|
1759
|
+
>['value']
|
|
1760
|
+
|
|
1761
|
+
x.tCh<StandardParsed, ExpectedSubj>()
|
|
1762
|
+
x.tCh<ExpectedSubj, StandardParsed>()
|
|
1763
|
+
|
|
1764
|
+
/* runtime schema check */
|
|
1765
|
+
|
|
1766
|
+
expect(struct.__schema).toStrictEqual(schema)
|
|
1767
|
+
expect(construct.__schema).toStrictEqual(schema)
|
|
1768
|
+
expect(construct.__schema === schema).toBe(false)
|
|
1769
|
+
|
|
1770
|
+
/* parse result check */
|
|
1771
|
+
|
|
1772
|
+
for (const subj of subjects) {
|
|
1773
|
+
const schemaParsed = x.parse(schema, subj)
|
|
1774
|
+
|
|
1775
|
+
expect(schemaParsed.error).toBe(undefined)
|
|
1776
|
+
expect(schemaParsed.data).toStrictEqual(subj)
|
|
1777
|
+
|
|
1778
|
+
const constructParsed = construct.parse(subj)
|
|
1779
|
+
|
|
1780
|
+
expect(constructParsed.error).toBe(undefined)
|
|
1781
|
+
expect(constructParsed.data).toStrictEqual(subj)
|
|
1782
|
+
|
|
1783
|
+
const structParsed = struct.parse(subj)
|
|
1784
|
+
|
|
1785
|
+
expect(structParsed.error).toBe(undefined)
|
|
1786
|
+
expect(structParsed.data).toStrictEqual(subj)
|
|
1787
|
+
|
|
1788
|
+
const standardParsed = struct['~standard'].validate(subj)
|
|
1789
|
+
|
|
1790
|
+
if (standardParsed instanceof Promise) {
|
|
1791
|
+
throw Error('Not expected')
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
if (standardParsed.issues !== undefined) {
|
|
1795
|
+
throw Error('not expected')
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
expect(standardParsed.value).toStrictEqual(subj)
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
})
|
|
1802
|
+
})
|