orchid-orm-schema-to-zod 0.0.1
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/README.md +5 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.esm.js +368 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +374 -0
- package/dist/index.js.map +1 -0
- package/jest-setup.ts +3 -0
- package/package.json +62 -0
- package/rollup.config.js +3 -0
- package/src/index.test.ts +1249 -0
- package/src/index.ts +667 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,1249 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArrayColumn,
|
|
3
|
+
ColumnType,
|
|
4
|
+
columnTypes as t,
|
|
5
|
+
DateColumn,
|
|
6
|
+
IntegerColumn,
|
|
7
|
+
jsonTypes,
|
|
8
|
+
TextColumn,
|
|
9
|
+
JSONType,
|
|
10
|
+
JSONTypeAny,
|
|
11
|
+
JSONDate,
|
|
12
|
+
JSONNumber,
|
|
13
|
+
JSONString,
|
|
14
|
+
JSONArray,
|
|
15
|
+
} from 'pqb';
|
|
16
|
+
import { columnToZod, instanceToZod, modelToZod } from './index';
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
import { Buffer } from 'node:buffer';
|
|
19
|
+
|
|
20
|
+
type AssertEqual<T, Expected> = [T] extends [Expected]
|
|
21
|
+
? [Expected] extends [T]
|
|
22
|
+
? true
|
|
23
|
+
: false
|
|
24
|
+
: false;
|
|
25
|
+
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
27
|
+
const assertType = <T, Expected>(_: AssertEqual<T, Expected>) => {
|
|
28
|
+
// noop
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
const columnOrJsonToZod = (type: any): z.ZodTypeAny => {
|
|
33
|
+
return type instanceof ColumnType
|
|
34
|
+
? columnToZod(type)
|
|
35
|
+
: columnToZod(t.json(() => type));
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
describe('model to zod', () => {
|
|
39
|
+
it('should convert a model to a zod validation schema', () => {
|
|
40
|
+
const model = class Model {
|
|
41
|
+
columns = {
|
|
42
|
+
shape: {
|
|
43
|
+
id: t.serial().primaryKey(),
|
|
44
|
+
name: t.text().nullable(),
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const result = modelToZod(model);
|
|
50
|
+
assertType<
|
|
51
|
+
typeof result,
|
|
52
|
+
z.ZodObject<{ id: z.ZodNumber; name: z.ZodNullable<z.ZodString> }>
|
|
53
|
+
>(true);
|
|
54
|
+
|
|
55
|
+
expect(result.parse({ id: 1, name: 'name' })).toEqual({
|
|
56
|
+
id: 1,
|
|
57
|
+
name: 'name',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(() => result.parse({ id: '1' })).toThrow(
|
|
61
|
+
'Expected number, received string',
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('instance to zod', () => {
|
|
67
|
+
it('should convert object with shape to a zod validation schema', () => {
|
|
68
|
+
const item = {
|
|
69
|
+
shape: {
|
|
70
|
+
id: t.serial().primaryKey(),
|
|
71
|
+
name: t.text().nullable(),
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const result = instanceToZod(item);
|
|
76
|
+
assertType<
|
|
77
|
+
typeof result,
|
|
78
|
+
z.ZodObject<{ id: z.ZodNumber; name: z.ZodNullable<z.ZodString> }>
|
|
79
|
+
>(true);
|
|
80
|
+
|
|
81
|
+
expect(result.parse({ id: 1, name: 'name' })).toEqual({
|
|
82
|
+
id: 1,
|
|
83
|
+
name: 'name',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(() => result.parse({ id: '1' })).toThrow(
|
|
87
|
+
'Expected number, received string',
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('schema to zod', () => {
|
|
93
|
+
describe('transform', () => {
|
|
94
|
+
it('should transform column value', () => {
|
|
95
|
+
const type = columnToZod(
|
|
96
|
+
t.text().transform((text) => text.split('').reverse().join('')),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(type.parse('123')).toBe('321');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should transform json value', () => {
|
|
103
|
+
const type = columnToZod(
|
|
104
|
+
t.json((t) =>
|
|
105
|
+
t.string().transform((text) => text.split('').reverse().join('')),
|
|
106
|
+
),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(type.parse('123')).toBe('321');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('to', () => {
|
|
114
|
+
it('should transform column value to a type', () => {
|
|
115
|
+
const type = columnToZod(t.text().to(parseInt, t.integer()));
|
|
116
|
+
expect(type.parse('123')).toBe(123);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should transform json value to a type', () => {
|
|
120
|
+
const type = columnToZod(
|
|
121
|
+
t.json((t) => t.string().to(parseInt, t.number())),
|
|
122
|
+
);
|
|
123
|
+
expect(type.parse('123')).toBe(123);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('refine', () => {
|
|
128
|
+
it('should add a refine check for column type', () => {
|
|
129
|
+
const type = columnToZod(t.text().refine((val) => val !== 'val'));
|
|
130
|
+
expect(() => type.parse('val')).toThrow('Invalid input');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should add a refine check for json type', () => {
|
|
134
|
+
const type = columnToZod(
|
|
135
|
+
t.json((t) => t.string().refine((val) => val !== 'val')),
|
|
136
|
+
);
|
|
137
|
+
expect(() => type.parse('val')).toThrow('Invalid input');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('superRefine', () => {
|
|
142
|
+
it('should add a superRefine check for column type', () => {
|
|
143
|
+
const type = columnToZod(
|
|
144
|
+
t.text().superRefine((val, ctx) => {
|
|
145
|
+
if (val.length > 3) {
|
|
146
|
+
ctx.addIssue({
|
|
147
|
+
code: z.ZodIssueCode.too_big,
|
|
148
|
+
maximum: 3,
|
|
149
|
+
type: 'string',
|
|
150
|
+
inclusive: true,
|
|
151
|
+
message: 'Too many items 😡',
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(() => type.parse('1234')).toThrow('Too many items');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should add a superRefine check for json type', () => {
|
|
161
|
+
const type = columnToZod(
|
|
162
|
+
t.json((t) =>
|
|
163
|
+
t.string().superRefine((val, ctx) => {
|
|
164
|
+
if (val.length > 3) {
|
|
165
|
+
ctx.addIssue({
|
|
166
|
+
code: z.ZodIssueCode.too_big,
|
|
167
|
+
maximum: 3,
|
|
168
|
+
type: 'string',
|
|
169
|
+
inclusive: true,
|
|
170
|
+
message: 'Too many items 😡',
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}),
|
|
174
|
+
),
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
expect(() => type.parse('1234')).toThrow('Too many items');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('validationDefault', () => {
|
|
182
|
+
it('should set default value for column', () => {
|
|
183
|
+
const type = columnToZod(t.text().validationDefault('value'));
|
|
184
|
+
|
|
185
|
+
expect(type.parse(undefined)).toBe('value');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should set default value for json type', () => {
|
|
189
|
+
const type = columnToZod(t.json((t) => t.string().default('value')));
|
|
190
|
+
|
|
191
|
+
expect(type.parse(undefined)).toBe('value');
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('nullable', () => {
|
|
196
|
+
it('should parse nullable', () => {
|
|
197
|
+
const schema = columnToZod(t.text().nullable());
|
|
198
|
+
|
|
199
|
+
assertType<typeof schema, z.ZodNullable<z.ZodString>>(true);
|
|
200
|
+
|
|
201
|
+
expect(schema.parse(null)).toBe(null);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const smallint = columnToZod(t.smallint());
|
|
206
|
+
const integer = columnToZod(t.integer());
|
|
207
|
+
const real = columnToZod(t.real());
|
|
208
|
+
const smallSerial = columnToZod(t.smallSerial());
|
|
209
|
+
const serial = columnToZod(t.serial());
|
|
210
|
+
const money = columnToZod(t.serial());
|
|
211
|
+
assertType<
|
|
212
|
+
| typeof smallint
|
|
213
|
+
| typeof integer
|
|
214
|
+
| typeof real
|
|
215
|
+
| typeof smallSerial
|
|
216
|
+
| typeof serial
|
|
217
|
+
| typeof money,
|
|
218
|
+
z.ZodNumber
|
|
219
|
+
>(true);
|
|
220
|
+
|
|
221
|
+
const testNumberMethods = (
|
|
222
|
+
type: IntegerColumn | JSONNumber,
|
|
223
|
+
isInt: boolean,
|
|
224
|
+
) => {
|
|
225
|
+
if (isInt) {
|
|
226
|
+
expect(() => columnOrJsonToZod(type).parse(1.5)).toThrow(
|
|
227
|
+
'Expected integer, received float',
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
expect(() => columnOrJsonToZod(type.lt(5)).parse(10)).toThrow(
|
|
232
|
+
'Number must be less than 5',
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
expect(() => columnOrJsonToZod(type.lte(5)).parse(10)).toThrow(
|
|
236
|
+
'Number must be less than or equal to 5',
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
expect(() => columnOrJsonToZod(type.max(5)).parse(10)).toThrow(
|
|
240
|
+
'Number must be less than or equal to 5',
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
expect(() => columnOrJsonToZod(type.gt(5)).parse(0)).toThrow(
|
|
244
|
+
'Number must be greater than 5',
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
expect(() => columnOrJsonToZod(type.gte(5)).parse(0)).toThrow(
|
|
248
|
+
'Number must be greater than or equal to 5',
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
expect(() => columnOrJsonToZod(type.min(5)).parse(0)).toThrow(
|
|
252
|
+
'Number must be greater than or equal to 5',
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
expect(() => columnOrJsonToZod(type.positive()).parse(-1)).toThrow(
|
|
256
|
+
'Number must be greater than 0',
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
expect(() => columnOrJsonToZod(type.nonNegative()).parse(-1)).toThrow(
|
|
260
|
+
'Number must be greater than or equal to 0',
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
expect(() => columnOrJsonToZod(type.negative()).parse(0)).toThrow(
|
|
264
|
+
'Number must be less than 0',
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
expect(() => columnOrJsonToZod(type.nonPositive()).parse(1)).toThrow(
|
|
268
|
+
'Number must be less than or equal to 0',
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
expect(() => columnOrJsonToZod(type.multipleOf(5)).parse(3)).toThrow(
|
|
272
|
+
'Number must be a multiple of 5',
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
expect(() => columnOrJsonToZod(type.step(5)).parse(3)).toThrow(
|
|
276
|
+
'Number must be a multiple of 5',
|
|
277
|
+
);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
describe.each([
|
|
281
|
+
'smallint',
|
|
282
|
+
'integer',
|
|
283
|
+
'real',
|
|
284
|
+
'smallSerial',
|
|
285
|
+
'serial',
|
|
286
|
+
'money',
|
|
287
|
+
])('%s', (method) => {
|
|
288
|
+
it('should convert to number', () => {
|
|
289
|
+
const schema = columnToZod(t[method as 'integer']());
|
|
290
|
+
|
|
291
|
+
expect(schema.parse(123)).toBe(123);
|
|
292
|
+
|
|
293
|
+
expect(() => schema.parse('s')).toThrow('Expected number');
|
|
294
|
+
|
|
295
|
+
testNumberMethods(
|
|
296
|
+
t[method as 'integer'](),
|
|
297
|
+
method !== 'real' && method !== 'money',
|
|
298
|
+
);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const bigint = columnToZod(t.bigint());
|
|
303
|
+
const numeric = columnToZod(t.numeric());
|
|
304
|
+
const decimal = columnToZod(t.decimal());
|
|
305
|
+
const doublePrecision = columnToZod(t.doublePrecision());
|
|
306
|
+
const bigSerial = columnToZod(t.bigSerial());
|
|
307
|
+
assertType<
|
|
308
|
+
| typeof bigint
|
|
309
|
+
| typeof numeric
|
|
310
|
+
| typeof decimal
|
|
311
|
+
| typeof doublePrecision
|
|
312
|
+
| typeof bigSerial,
|
|
313
|
+
z.ZodString
|
|
314
|
+
>(true);
|
|
315
|
+
|
|
316
|
+
describe.each([
|
|
317
|
+
'bigint',
|
|
318
|
+
'numeric',
|
|
319
|
+
'decimal',
|
|
320
|
+
'doublePrecision',
|
|
321
|
+
'bigSerial',
|
|
322
|
+
])('%s', (method) => {
|
|
323
|
+
it('should validate bigint and parse to a string', () => {
|
|
324
|
+
const schema = columnToZod(t[method as 'bigint']());
|
|
325
|
+
|
|
326
|
+
expect(schema.parse('123')).toBe('123');
|
|
327
|
+
|
|
328
|
+
expect(() => schema.parse('s')).toThrow('Failed to parse bigint');
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const varchar = columnToZod(t.varchar());
|
|
333
|
+
const char = columnToZod(t.char());
|
|
334
|
+
const text = columnToZod(t.text());
|
|
335
|
+
const string = columnToZod(t.string());
|
|
336
|
+
assertType<
|
|
337
|
+
typeof varchar | typeof char | typeof text | typeof string,
|
|
338
|
+
z.ZodString
|
|
339
|
+
>(true);
|
|
340
|
+
|
|
341
|
+
const testStringMethods = (type: TextColumn | JSONString) => {
|
|
342
|
+
expect(() => columnOrJsonToZod(type.min(1)).parse('')).toThrow(
|
|
343
|
+
'String must contain at least 1 character(s)',
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
expect(() => columnOrJsonToZod(type.max(1)).parse('123')).toThrow(
|
|
347
|
+
'String must contain at most 1 character(s)',
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
expect(() => columnOrJsonToZod(type.length(1)).parse('')).toThrow(
|
|
351
|
+
'String must contain at least 1 character(s)',
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
expect(() => columnOrJsonToZod(type.length(1)).parse('123')).toThrow(
|
|
355
|
+
'String must contain at most 1 character(s)',
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
expect(() => columnOrJsonToZod(type.email()).parse('invalid')).toThrow(
|
|
359
|
+
'Invalid email',
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
expect(() => columnOrJsonToZod(type.url()).parse('invalid')).toThrow(
|
|
363
|
+
'Invalid url',
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
expect(() => columnOrJsonToZod(type.uuid()).parse('invalid')).toThrow(
|
|
367
|
+
'Invalid uuid',
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
expect(() => columnOrJsonToZod(type.cuid()).parse('invalid')).toThrow(
|
|
371
|
+
'Invalid cuid',
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
expect(columnOrJsonToZod(type.trim()).parse(' trimmed ')).toBe('trimmed');
|
|
375
|
+
|
|
376
|
+
expect(() => columnOrJsonToZod(type.nonempty()).parse('')).toThrow(
|
|
377
|
+
'String must contain at least 1 character(s)',
|
|
378
|
+
);
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
describe.each(['varchar', 'char', 'text', 'string'])('%s', (method) => {
|
|
382
|
+
it('should convert to string', () => {
|
|
383
|
+
const schema = columnToZod(t[method as 'text']());
|
|
384
|
+
|
|
385
|
+
expect(schema.parse('s')).toBe('s');
|
|
386
|
+
|
|
387
|
+
expect(() => schema.parse(1)).toThrow('Expected string');
|
|
388
|
+
|
|
389
|
+
testStringMethods(t[method as 'text']());
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
describe('bytea', () => {
|
|
394
|
+
it('should check Buffer', () => {
|
|
395
|
+
const schema = columnToZod(t.bytea());
|
|
396
|
+
|
|
397
|
+
assertType<typeof schema, z.ZodType<Buffer>>(true);
|
|
398
|
+
|
|
399
|
+
const buffer = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);
|
|
400
|
+
expect(schema.parse(buffer)).toBe(buffer);
|
|
401
|
+
|
|
402
|
+
expect(() => schema.parse([1, 0, 1])).toThrow(
|
|
403
|
+
'Input not instance of Buffer',
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
const date = columnToZod(t.date());
|
|
409
|
+
const timestamp = columnToZod(t.timestamp());
|
|
410
|
+
const timestampWithTimeZone = columnToZod(t.timestampWithTimeZone());
|
|
411
|
+
assertType<
|
|
412
|
+
typeof date | typeof timestamp | typeof timestampWithTimeZone,
|
|
413
|
+
z.ZodDate
|
|
414
|
+
>(true);
|
|
415
|
+
|
|
416
|
+
const testDateMethods = (type: DateColumn | JSONDate) => {
|
|
417
|
+
const now = new Date();
|
|
418
|
+
|
|
419
|
+
expect(() =>
|
|
420
|
+
columnOrJsonToZod(type.min(new Date(now.getTime() + 100))).parse(now),
|
|
421
|
+
).toThrow('Date must be greater than or equal to');
|
|
422
|
+
|
|
423
|
+
expect(() =>
|
|
424
|
+
columnOrJsonToZod(type.max(new Date(now.getTime() - 100))).parse(now),
|
|
425
|
+
).toThrow('Date must be smaller than or equal to');
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
describe.each(['date', 'timestamp', 'timestampWithTimeZone'])(
|
|
429
|
+
'%s',
|
|
430
|
+
(method) => {
|
|
431
|
+
it('should parse from string to a Date', () => {
|
|
432
|
+
const schema = columnToZod(t[method as 'date']());
|
|
433
|
+
|
|
434
|
+
const date = new Date(2000, 0, 1, 0, 0, 0, 0);
|
|
435
|
+
expect(schema.parse(date)).toEqual(date);
|
|
436
|
+
|
|
437
|
+
expect(() => schema.parse('malformed')).toThrow('Invalid date');
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('should parse from Date to a Date', () => {
|
|
441
|
+
const schema = columnToZod(t[method as 'date']());
|
|
442
|
+
|
|
443
|
+
assertType<typeof schema, z.ZodDate>(true);
|
|
444
|
+
|
|
445
|
+
const date = new Date(2000, 0, 1, 0, 0, 0, 0);
|
|
446
|
+
expect(schema.parse(date)).toEqual(date);
|
|
447
|
+
|
|
448
|
+
testDateMethods(t[method as 'date']());
|
|
449
|
+
});
|
|
450
|
+
},
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
const time = columnToZod(t.time());
|
|
454
|
+
const timeWithTimeZone = columnToZod(t.timeWithTimeZone());
|
|
455
|
+
assertType<typeof time | typeof timeWithTimeZone, z.ZodString>(true);
|
|
456
|
+
|
|
457
|
+
describe.each(['time', 'timeWithTimeZone'])('%s', (method) => {
|
|
458
|
+
it('should validate and parse to a string', () => {
|
|
459
|
+
const schema = columnToZod(t[method as 'time']());
|
|
460
|
+
|
|
461
|
+
const input = method === 'time' ? '12:12:12' : '12:12:12.1234 +00:00';
|
|
462
|
+
expect(schema.parse(input)).toBe(input);
|
|
463
|
+
|
|
464
|
+
expect(() => schema.parse('malformed')).toThrow('Invalid time');
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
describe('interval', () => {
|
|
469
|
+
it('should validate and parse time interval', () => {
|
|
470
|
+
const schema = columnToZod(t.interval());
|
|
471
|
+
|
|
472
|
+
const interval = {
|
|
473
|
+
years: 1,
|
|
474
|
+
months: 1,
|
|
475
|
+
days: 1,
|
|
476
|
+
hours: 1,
|
|
477
|
+
seconds: 1,
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
assertType<ReturnType<typeof schema['parse']>, Partial<typeof interval>>(
|
|
481
|
+
true,
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
expect(schema.parse(interval)).toEqual(interval);
|
|
485
|
+
|
|
486
|
+
expect(() => schema.parse({ years: 'string' })).toThrow(
|
|
487
|
+
'Expected number, received string',
|
|
488
|
+
);
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
describe('boolean', () => {
|
|
493
|
+
it('should validate and parse a boolean', () => {
|
|
494
|
+
const schema = columnToZod(t.boolean());
|
|
495
|
+
|
|
496
|
+
assertType<typeof schema, z.ZodBoolean>(true);
|
|
497
|
+
|
|
498
|
+
expect(schema.parse(true)).toBe(true);
|
|
499
|
+
|
|
500
|
+
expect(() => schema.parse(123)).toThrow(
|
|
501
|
+
'Expected boolean, received number',
|
|
502
|
+
);
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
describe('enum', () => {
|
|
507
|
+
it('should validate and parse enum', () => {
|
|
508
|
+
const schema = columnToZod(t.enum('name', ['a', 'b', 'c']));
|
|
509
|
+
|
|
510
|
+
assertType<typeof schema, z.ZodEnum<['a', 'b', 'c']>>(true);
|
|
511
|
+
|
|
512
|
+
expect(schema.parse('a')).toBe('a');
|
|
513
|
+
|
|
514
|
+
expect(() => schema.parse('d')).toThrow('Invalid enum value');
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
const point = columnToZod(t.point());
|
|
519
|
+
const line = columnToZod(t.line());
|
|
520
|
+
const lseg = columnToZod(t.lseg());
|
|
521
|
+
const box = columnToZod(t.box());
|
|
522
|
+
const path = columnToZod(t.path());
|
|
523
|
+
const polygon = columnToZod(t.polygon());
|
|
524
|
+
const circle = columnToZod(t.circle());
|
|
525
|
+
assertType<
|
|
526
|
+
| typeof point
|
|
527
|
+
| typeof line
|
|
528
|
+
| typeof lseg
|
|
529
|
+
| typeof box
|
|
530
|
+
| typeof path
|
|
531
|
+
| typeof polygon
|
|
532
|
+
| typeof circle,
|
|
533
|
+
z.ZodString
|
|
534
|
+
>(true);
|
|
535
|
+
|
|
536
|
+
describe.each(['point', 'line', 'lseg', 'box', 'path', 'polygon', 'circle'])(
|
|
537
|
+
'%s',
|
|
538
|
+
(method) => {
|
|
539
|
+
it('should parse to a string without validation', () => {
|
|
540
|
+
const schema = columnToZod(t[method as 'point']());
|
|
541
|
+
|
|
542
|
+
expect(schema.parse('string')).toBe('string');
|
|
543
|
+
|
|
544
|
+
expect(() => schema.parse(123)).toThrow(
|
|
545
|
+
'Expected string, received number',
|
|
546
|
+
);
|
|
547
|
+
});
|
|
548
|
+
},
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
const cidr = columnToZod(t.cidr());
|
|
552
|
+
const inet = columnToZod(t.inet());
|
|
553
|
+
const macaddr = columnToZod(t.macaddr());
|
|
554
|
+
const macaddr8 = columnToZod(t.macaddr8());
|
|
555
|
+
assertType<
|
|
556
|
+
typeof cidr | typeof inet | typeof macaddr | typeof macaddr8,
|
|
557
|
+
z.ZodString
|
|
558
|
+
>(true);
|
|
559
|
+
|
|
560
|
+
describe.each(['cidr', 'inet', 'macaddr', 'macaddr8'])('%s', (method) => {
|
|
561
|
+
it('should parse to a string without validation', () => {
|
|
562
|
+
const schema = columnToZod(t[method as 'cidr']());
|
|
563
|
+
|
|
564
|
+
expect(schema.parse('string')).toBe('string');
|
|
565
|
+
|
|
566
|
+
expect(() => schema.parse(123)).toThrow(
|
|
567
|
+
'Expected string, received number',
|
|
568
|
+
);
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
const bit = columnToZod(t.bit(5));
|
|
573
|
+
const bitVarying = columnToZod(t.bitVarying());
|
|
574
|
+
assertType<typeof bit | typeof bitVarying, z.ZodString>(true);
|
|
575
|
+
|
|
576
|
+
describe.each(['bit', 'bitVarying'])('%s', (method) => {
|
|
577
|
+
it('should validate a string to contain only 1 or 0 and parse to a string', () => {
|
|
578
|
+
const schema = columnToZod(t[method as 'bit'](5));
|
|
579
|
+
|
|
580
|
+
expect(schema.parse('10101')).toBe('10101');
|
|
581
|
+
|
|
582
|
+
expect(() => schema.parse('2')).toThrow('Invalid');
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
const tsvector = columnToZod(t.tsvector());
|
|
587
|
+
const tsquery = columnToZod(t.tsquery());
|
|
588
|
+
assertType<typeof tsvector | typeof tsquery, z.ZodString>(true);
|
|
589
|
+
|
|
590
|
+
describe.each(['tsvector', 'tsquery'])('%s', (method) => {
|
|
591
|
+
it('should parse to a string without validation', () => {
|
|
592
|
+
const schema = columnToZod(t[method as 'tsvector']());
|
|
593
|
+
|
|
594
|
+
expect(schema.parse('string')).toBe('string');
|
|
595
|
+
|
|
596
|
+
expect(() => schema.parse(123)).toThrow(
|
|
597
|
+
'Expected string, received number',
|
|
598
|
+
);
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
const xml = columnToZod(t.xml());
|
|
603
|
+
const jsonText = columnToZod(t.jsonText());
|
|
604
|
+
assertType<typeof xml | typeof jsonText, z.ZodString>(true);
|
|
605
|
+
|
|
606
|
+
describe.each(['xml', 'jsonText'])('%s', (method) => {
|
|
607
|
+
it('should parse to a string without validation', () => {
|
|
608
|
+
const schema = columnToZod(t[method as 'xml']());
|
|
609
|
+
|
|
610
|
+
expect(schema.parse('string')).toBe('string');
|
|
611
|
+
|
|
612
|
+
expect(() => schema.parse(123)).toThrow(
|
|
613
|
+
'Expected string, received number',
|
|
614
|
+
);
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
describe('uuid', () => {
|
|
619
|
+
it('should validate uuid and parse to a string', () => {
|
|
620
|
+
const schema = columnToZod(t.uuid());
|
|
621
|
+
|
|
622
|
+
assertType<typeof schema, z.ZodString>(true);
|
|
623
|
+
|
|
624
|
+
const uuid = '123e4567-e89b-12d3-a456-426614174000';
|
|
625
|
+
expect(schema.parse(uuid)).toBe(uuid);
|
|
626
|
+
|
|
627
|
+
expect(() => schema.parse('1234')).toThrow('Invalid uuid');
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
const testArrayMethods = (
|
|
632
|
+
type: ArrayColumn<ColumnType> | JSONArray<JSONTypeAny>,
|
|
633
|
+
) => {
|
|
634
|
+
expect(() => columnOrJsonToZod(type.min(1)).parse([])).toThrow(
|
|
635
|
+
'Array must contain at least 1 element(s)',
|
|
636
|
+
);
|
|
637
|
+
|
|
638
|
+
expect(() => columnOrJsonToZod(type.max(1)).parse([1, 2])).toThrow(
|
|
639
|
+
'Array must contain at most 1 element(s)',
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
expect(() => columnOrJsonToZod(type.length(1)).parse([])).toThrow(
|
|
643
|
+
'Array must contain at least 1 element(s)',
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
expect(() => columnOrJsonToZod(type.length(1)).parse([1, 2])).toThrow(
|
|
647
|
+
'Array must contain at most 1 element(s)',
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
expect(() => columnOrJsonToZod(type.nonempty()).parse([])).toThrow(
|
|
651
|
+
'Array must contain at least 1 element(s)',
|
|
652
|
+
);
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
describe('array', () => {
|
|
656
|
+
it('should validate and parse array', () => {
|
|
657
|
+
const schema = columnToZod(t.array(t.integer()));
|
|
658
|
+
|
|
659
|
+
assertType<typeof schema, z.ZodArray<z.ZodNumber>>(true);
|
|
660
|
+
|
|
661
|
+
expect(schema.parse([1, 2, 3])).toEqual([1, 2, 3]);
|
|
662
|
+
|
|
663
|
+
expect(() => schema.parse(123)).toThrow(
|
|
664
|
+
'Expected array, received number',
|
|
665
|
+
);
|
|
666
|
+
expect(() => schema.parse(['a'])).toThrow(
|
|
667
|
+
'Expected number, received string',
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
testArrayMethods(t.array(t.integer()));
|
|
671
|
+
});
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
describe('json', () => {
|
|
675
|
+
describe('any', () => {
|
|
676
|
+
it('should parse to any', () => {
|
|
677
|
+
const schema = columnToZod(t.json((t) => t.any()));
|
|
678
|
+
|
|
679
|
+
assertType<typeof schema, z.ZodTypeAny>(true);
|
|
680
|
+
|
|
681
|
+
expect(schema.parse(123)).toBe(123);
|
|
682
|
+
});
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
describe('bigint', () => {
|
|
686
|
+
it('should validate bigint and parse to string', () => {
|
|
687
|
+
const schema = columnToZod(t.json((t) => t.bigint()));
|
|
688
|
+
|
|
689
|
+
assertType<typeof schema, z.ZodString>(true);
|
|
690
|
+
|
|
691
|
+
expect(schema.parse('123')).toBe('123');
|
|
692
|
+
|
|
693
|
+
expect(() => schema.parse('kokoko')).toThrow('Failed to parse bigint');
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
describe('boolean', () => {
|
|
698
|
+
it('should parse boolean', () => {
|
|
699
|
+
const schema = columnToZod(t.json((t) => t.boolean()));
|
|
700
|
+
|
|
701
|
+
assertType<typeof schema, z.ZodBoolean>(true);
|
|
702
|
+
|
|
703
|
+
expect(schema.parse(true)).toBe(true);
|
|
704
|
+
|
|
705
|
+
expect(() => schema.parse(123)).toThrow(
|
|
706
|
+
'Expected boolean, received number',
|
|
707
|
+
);
|
|
708
|
+
});
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
describe('date', () => {
|
|
712
|
+
it('should parse a Date', () => {
|
|
713
|
+
const schema = columnToZod(t.json((t) => t.date()));
|
|
714
|
+
|
|
715
|
+
assertType<typeof schema, z.ZodDate>(true);
|
|
716
|
+
|
|
717
|
+
const date = new Date(2000, 0, 1);
|
|
718
|
+
expect(schema.parse(date).getTime()).toBe(date.getTime());
|
|
719
|
+
|
|
720
|
+
expect(() => schema.parse(new Date('koko'))).toThrow('Invalid date');
|
|
721
|
+
|
|
722
|
+
testDateMethods(jsonTypes.date());
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
describe('nan', () => {
|
|
727
|
+
it('should parse a NaN', () => {
|
|
728
|
+
const schema = columnToZod(t.json((t) => t.nan()));
|
|
729
|
+
|
|
730
|
+
assertType<typeof schema, z.ZodNaN>(true);
|
|
731
|
+
|
|
732
|
+
expect(schema.parse(NaN)).toBe(NaN);
|
|
733
|
+
|
|
734
|
+
expect(() => schema.parse(123)).toThrow(
|
|
735
|
+
'Expected nan, received number',
|
|
736
|
+
);
|
|
737
|
+
});
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
describe('never', () => {
|
|
741
|
+
it('should parse a never', () => {
|
|
742
|
+
const schema = columnToZod(t.json((t) => t.never()));
|
|
743
|
+
|
|
744
|
+
assertType<typeof schema, z.ZodNever>(true);
|
|
745
|
+
|
|
746
|
+
expect(() => schema.parse(123)).toThrow(
|
|
747
|
+
'Expected never, received number',
|
|
748
|
+
);
|
|
749
|
+
});
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
describe('null', () => {
|
|
753
|
+
it('should parse a null', () => {
|
|
754
|
+
const schema = columnToZod(t.json((t) => t.null()));
|
|
755
|
+
|
|
756
|
+
assertType<typeof schema, z.ZodNull>(true);
|
|
757
|
+
|
|
758
|
+
expect(schema.parse(null)).toBe(null);
|
|
759
|
+
|
|
760
|
+
expect(() => schema.parse(123)).toThrow(
|
|
761
|
+
'Expected null, received number',
|
|
762
|
+
);
|
|
763
|
+
});
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
describe('number', () => {
|
|
767
|
+
it('should parse a number', () => {
|
|
768
|
+
const schema = columnToZod(t.json((t) => t.number()));
|
|
769
|
+
|
|
770
|
+
assertType<typeof schema, z.ZodNumber>(true);
|
|
771
|
+
|
|
772
|
+
expect(schema.parse(123)).toBe(123);
|
|
773
|
+
|
|
774
|
+
expect(() => schema.parse('123')).toThrow(
|
|
775
|
+
'Expected number, received string',
|
|
776
|
+
);
|
|
777
|
+
|
|
778
|
+
testNumberMethods(jsonTypes.number().int(), true);
|
|
779
|
+
});
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
describe('string', () => {
|
|
783
|
+
it('should parse a string', () => {
|
|
784
|
+
const schema = columnToZod(t.json((t) => t.string()));
|
|
785
|
+
|
|
786
|
+
assertType<typeof schema, z.ZodString>(true);
|
|
787
|
+
|
|
788
|
+
expect(schema.parse('string')).toBe('string');
|
|
789
|
+
|
|
790
|
+
expect(() => schema.parse(123)).toThrow(
|
|
791
|
+
'Expected string, received number',
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
testStringMethods(jsonTypes.string());
|
|
795
|
+
});
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
describe('undefined', () => {
|
|
799
|
+
it('should parse a undefined', () => {
|
|
800
|
+
const schema = columnToZod(t.json((t) => t.undefined()));
|
|
801
|
+
|
|
802
|
+
assertType<typeof schema, z.ZodUndefined>(true);
|
|
803
|
+
|
|
804
|
+
expect(schema.parse(undefined)).toBe(undefined);
|
|
805
|
+
|
|
806
|
+
expect(() => schema.parse(123)).toThrow(
|
|
807
|
+
'Expected undefined, received number',
|
|
808
|
+
);
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
describe('unknown', () => {
|
|
813
|
+
it('should parse unknown', () => {
|
|
814
|
+
const schema = columnToZod(t.json((t) => t.unknown()));
|
|
815
|
+
|
|
816
|
+
assertType<typeof schema, z.ZodUnknown>(true);
|
|
817
|
+
|
|
818
|
+
expect(schema.parse(123)).toBe(123);
|
|
819
|
+
});
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
describe('void', () => {
|
|
823
|
+
it('should parse void', () => {
|
|
824
|
+
const schema = columnToZod(t.json((t) => t.void()));
|
|
825
|
+
|
|
826
|
+
assertType<typeof schema, z.ZodVoid>(true);
|
|
827
|
+
|
|
828
|
+
expect(schema.parse(undefined)).toBe(undefined);
|
|
829
|
+
|
|
830
|
+
expect(() => schema.parse(123)).toThrow(
|
|
831
|
+
'Expected void, received number',
|
|
832
|
+
);
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
describe('array', () => {
|
|
837
|
+
it('should validate and parse array', () => {
|
|
838
|
+
const schema = columnToZod(t.json((t) => t.array(t.number())));
|
|
839
|
+
|
|
840
|
+
assertType<typeof schema, z.ZodArray<z.ZodNumber>>(true);
|
|
841
|
+
|
|
842
|
+
expect(schema.parse([1, 2, 3])).toEqual([1, 2, 3]);
|
|
843
|
+
|
|
844
|
+
expect(() => schema.parse(123)).toThrow(
|
|
845
|
+
'Expected array, received number',
|
|
846
|
+
);
|
|
847
|
+
|
|
848
|
+
expect(() => schema.parse(['a'])).toThrow(
|
|
849
|
+
'Expected number, received string',
|
|
850
|
+
);
|
|
851
|
+
|
|
852
|
+
testArrayMethods(jsonTypes.array(jsonTypes.number()));
|
|
853
|
+
});
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
describe('enum', () => {
|
|
857
|
+
it('should parse enum', () => {
|
|
858
|
+
const schema = columnToZod(t.json((t) => t.enum(['a', 'b', 'c'])));
|
|
859
|
+
|
|
860
|
+
assertType<typeof schema, z.ZodEnum<['a', 'b', 'c']>>(true);
|
|
861
|
+
|
|
862
|
+
expect(schema.parse('a')).toBe('a');
|
|
863
|
+
|
|
864
|
+
expect(() => schema.parse('d')).toThrow('Invalid enum value');
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
describe('instanceOf', () => {
|
|
869
|
+
it('should parse instance of', () => {
|
|
870
|
+
const schema = columnToZod(t.json((t) => t.instanceOf(Date)));
|
|
871
|
+
|
|
872
|
+
assertType<typeof schema, z.ZodType<Date, z.ZodTypeDef, Date>>(true);
|
|
873
|
+
|
|
874
|
+
const date = new Date();
|
|
875
|
+
expect(schema.parse(date)).toBe(date);
|
|
876
|
+
|
|
877
|
+
expect(() => schema.parse({})).toThrow('Input not instance of Date');
|
|
878
|
+
});
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
describe('literal', () => {
|
|
882
|
+
it('should parse literal', () => {
|
|
883
|
+
const schema = columnToZod(t.json((t) => t.literal('string')));
|
|
884
|
+
|
|
885
|
+
assertType<typeof schema, z.ZodLiteral<'string'>>(true);
|
|
886
|
+
|
|
887
|
+
expect(schema.parse('string')).toBe('string');
|
|
888
|
+
|
|
889
|
+
expect(() => schema.parse('koko')).toThrow('Invalid literal value');
|
|
890
|
+
});
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
describe('map', () => {
|
|
894
|
+
it('should parse map', () => {
|
|
895
|
+
const schema = columnToZod(
|
|
896
|
+
t.json((t) => t.map(t.string(), t.number())),
|
|
897
|
+
);
|
|
898
|
+
|
|
899
|
+
assertType<typeof schema, z.ZodMap<z.ZodString, z.ZodNumber>>(true);
|
|
900
|
+
|
|
901
|
+
const map = new Map();
|
|
902
|
+
map.set('key', 123);
|
|
903
|
+
expect(schema.parse(map)).toEqual(map);
|
|
904
|
+
|
|
905
|
+
map.set(123, 'key');
|
|
906
|
+
expect(() => schema.parse(map)).toThrow(
|
|
907
|
+
'Expected number, received string',
|
|
908
|
+
);
|
|
909
|
+
});
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
describe('set', () => {
|
|
913
|
+
it('should parse set', () => {
|
|
914
|
+
const schema = columnToZod(t.json((t) => t.set(t.number())));
|
|
915
|
+
|
|
916
|
+
assertType<typeof schema, z.ZodSet<z.ZodNumber>>(true);
|
|
917
|
+
|
|
918
|
+
const set = new Set();
|
|
919
|
+
set.add(1);
|
|
920
|
+
expect(schema.parse(set)).toEqual(set);
|
|
921
|
+
|
|
922
|
+
set.add('string');
|
|
923
|
+
expect(() => schema.parse(set)).toThrow(
|
|
924
|
+
'Expected number, received string',
|
|
925
|
+
);
|
|
926
|
+
|
|
927
|
+
const type = jsonTypes.set(jsonTypes.number());
|
|
928
|
+
expect(() => columnOrJsonToZod(type.min(1)).parse(new Set())).toThrow(
|
|
929
|
+
'Invalid input',
|
|
930
|
+
);
|
|
931
|
+
|
|
932
|
+
expect(() =>
|
|
933
|
+
columnOrJsonToZod(type.max(1)).parse(new Set([1, 2])),
|
|
934
|
+
).toThrow('Invalid input');
|
|
935
|
+
|
|
936
|
+
expect(() => columnOrJsonToZod(type.size(1)).parse(new Set())).toThrow(
|
|
937
|
+
'Invalid input',
|
|
938
|
+
);
|
|
939
|
+
|
|
940
|
+
expect(() =>
|
|
941
|
+
columnOrJsonToZod(type.size(1)).parse(new Set([1, 2])),
|
|
942
|
+
).toThrow('Invalid input');
|
|
943
|
+
|
|
944
|
+
expect(() =>
|
|
945
|
+
columnOrJsonToZod(type.nonempty()).parse(new Set()),
|
|
946
|
+
).toThrow('Invalid input');
|
|
947
|
+
});
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
describe('nativeEnum', () => {
|
|
951
|
+
it('should parse native enum', () => {
|
|
952
|
+
enum Test {
|
|
953
|
+
one = 'one',
|
|
954
|
+
two = 'two',
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
const schema = columnToZod(t.json((t) => t.nativeEnum(Test)));
|
|
958
|
+
|
|
959
|
+
assertType<typeof schema, z.ZodNativeEnum<typeof Test>>(true);
|
|
960
|
+
|
|
961
|
+
expect(schema.parse('one')).toBe('one');
|
|
962
|
+
|
|
963
|
+
expect(() => schema.parse('ko')).toThrow('Invalid enum value');
|
|
964
|
+
});
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
describe('tuple', () => {
|
|
968
|
+
it('should parse tuple', () => {
|
|
969
|
+
const schema = columnToZod(
|
|
970
|
+
t.json((t) => t.tuple([t.number(), t.string()])),
|
|
971
|
+
);
|
|
972
|
+
|
|
973
|
+
assertType<typeof schema, z.ZodTuple<[z.ZodNumber, z.ZodString]>>(true);
|
|
974
|
+
|
|
975
|
+
expect(schema.parse([1, 'string'])).toEqual([1, 'string']);
|
|
976
|
+
|
|
977
|
+
expect(() => schema.parse(['string', 1])).toThrow(
|
|
978
|
+
`Expected number, received string`,
|
|
979
|
+
);
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
it('should parse rest elements', () => {
|
|
983
|
+
const schema = columnToZod(
|
|
984
|
+
t.json((t) => t.tuple([t.number()]).rest(t.string())),
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
assertType<typeof schema, z.ZodTuple<[z.ZodNumber], z.ZodString>>(true);
|
|
988
|
+
|
|
989
|
+
expect(schema.parse([1, 'a', 'b'])).toEqual([1, 'a', 'b']);
|
|
990
|
+
|
|
991
|
+
expect(() => schema.parse([1, 'a', 2])).toThrow(
|
|
992
|
+
'Expected string, received number',
|
|
993
|
+
);
|
|
994
|
+
});
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
describe('nullable', () => {
|
|
998
|
+
it('should parse nullable', () => {
|
|
999
|
+
const schema = columnToZod(t.json((t) => t.nullable(t.number())));
|
|
1000
|
+
|
|
1001
|
+
assertType<typeof schema, z.ZodNullable<z.ZodNumber>>(true);
|
|
1002
|
+
|
|
1003
|
+
expect(schema.parse(null)).toBe(null);
|
|
1004
|
+
});
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
describe('nullish', () => {
|
|
1008
|
+
it('should parse nullish', () => {
|
|
1009
|
+
const schema = columnToZod(t.json((t) => t.nullish(t.number())));
|
|
1010
|
+
|
|
1011
|
+
assertType<typeof schema, z.ZodNullable<z.ZodOptional<z.ZodNumber>>>(
|
|
1012
|
+
true,
|
|
1013
|
+
);
|
|
1014
|
+
|
|
1015
|
+
expect(schema.parse(null)).toBe(null);
|
|
1016
|
+
expect(schema.parse(undefined)).toBe(undefined);
|
|
1017
|
+
});
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
describe('optional', () => {
|
|
1021
|
+
it('should parse optional', () => {
|
|
1022
|
+
const schema = columnToZod(t.json((t) => t.optional(t.number())));
|
|
1023
|
+
|
|
1024
|
+
assertType<typeof schema, z.ZodOptional<z.ZodNumber>>(true);
|
|
1025
|
+
|
|
1026
|
+
expect(schema.parse(undefined)).toBe(undefined);
|
|
1027
|
+
});
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
describe('object', () => {
|
|
1031
|
+
it('should parse object', () => {
|
|
1032
|
+
const schema = columnToZod(
|
|
1033
|
+
t.json((t) => t.object({ key: t.number() })),
|
|
1034
|
+
);
|
|
1035
|
+
|
|
1036
|
+
assertType<typeof schema, z.ZodObject<{ key: z.ZodNumber }>>(true);
|
|
1037
|
+
|
|
1038
|
+
expect(schema.parse({ key: 123 })).toEqual({ key: 123 });
|
|
1039
|
+
|
|
1040
|
+
expect(() => schema.parse({ key: 'string' })).toThrow(
|
|
1041
|
+
'Expected number, received string',
|
|
1042
|
+
);
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
it('should parse object with passing through unknown keys', () => {
|
|
1046
|
+
const schema = columnToZod(
|
|
1047
|
+
t.json((t) => t.object({ key: t.number() }).passthrough()),
|
|
1048
|
+
);
|
|
1049
|
+
|
|
1050
|
+
assertType<
|
|
1051
|
+
typeof schema,
|
|
1052
|
+
z.ZodObject<{ key: z.ZodNumber }, 'passthrough'>
|
|
1053
|
+
>(true);
|
|
1054
|
+
|
|
1055
|
+
expect(schema.parse({ key: 123, koko: 'koko' })).toEqual({
|
|
1056
|
+
key: 123,
|
|
1057
|
+
koko: 'koko',
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
expect(() => schema.parse({ key: 'string' })).toThrow(
|
|
1061
|
+
'Expected number, received string',
|
|
1062
|
+
);
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
it('should parse object with strict unknown keys', () => {
|
|
1066
|
+
const schema = columnToZod(
|
|
1067
|
+
t.json((t) => t.object({ key: t.number() }).strict()),
|
|
1068
|
+
);
|
|
1069
|
+
|
|
1070
|
+
assertType<typeof schema, z.ZodObject<{ key: z.ZodNumber }, 'strict'>>(
|
|
1071
|
+
true,
|
|
1072
|
+
);
|
|
1073
|
+
|
|
1074
|
+
expect(schema.parse({ key: 123 })).toEqual({ key: 123 });
|
|
1075
|
+
|
|
1076
|
+
expect(() => schema.parse({ key: 123, koko: 'koko' })).toThrow(
|
|
1077
|
+
'Unrecognized key(s)',
|
|
1078
|
+
);
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
it('should parse object with catch all option', () => {
|
|
1082
|
+
const schema = columnToZod(
|
|
1083
|
+
t.json((t) => t.object({ key: t.number() }).catchAll(t.number())),
|
|
1084
|
+
);
|
|
1085
|
+
|
|
1086
|
+
assertType<
|
|
1087
|
+
typeof schema,
|
|
1088
|
+
z.ZodObject<{ key: z.ZodNumber }, 'strip', z.ZodNumber>
|
|
1089
|
+
>(true);
|
|
1090
|
+
|
|
1091
|
+
expect(schema.parse({ key: 123, koko: 123 })).toEqual({
|
|
1092
|
+
key: 123,
|
|
1093
|
+
koko: 123,
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
expect(() => schema.parse({ key: 123, koko: 'koko' })).toThrow(
|
|
1097
|
+
'Expected number, received string',
|
|
1098
|
+
);
|
|
1099
|
+
});
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
describe('record', () => {
|
|
1103
|
+
it('should parse record', () => {
|
|
1104
|
+
const schema = columnToZod(
|
|
1105
|
+
t.json((t) => t.record(t.string(), t.number())),
|
|
1106
|
+
);
|
|
1107
|
+
|
|
1108
|
+
assertType<typeof schema, z.ZodRecord<z.ZodString, z.ZodNumber>>(true);
|
|
1109
|
+
|
|
1110
|
+
expect(schema.parse({ key: 123 })).toEqual({ key: 123 });
|
|
1111
|
+
|
|
1112
|
+
expect(() => schema.parse({ key: 'string' })).toThrow(
|
|
1113
|
+
'Expected number, received string',
|
|
1114
|
+
);
|
|
1115
|
+
});
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
describe('intersection', () => {
|
|
1119
|
+
it('should parse intersection', () => {
|
|
1120
|
+
const schema = columnToZod(
|
|
1121
|
+
t.json((t) =>
|
|
1122
|
+
t.intersection(
|
|
1123
|
+
t.object({ a: t.string(), b: t.number() }),
|
|
1124
|
+
t.object({ a: t.string(), c: t.number() }),
|
|
1125
|
+
),
|
|
1126
|
+
),
|
|
1127
|
+
);
|
|
1128
|
+
|
|
1129
|
+
assertType<
|
|
1130
|
+
typeof schema,
|
|
1131
|
+
z.ZodIntersection<
|
|
1132
|
+
z.ZodObject<{ a: z.ZodString; b: z.ZodNumber }>,
|
|
1133
|
+
z.ZodObject<{ a: z.ZodString; c: z.ZodNumber }>
|
|
1134
|
+
>
|
|
1135
|
+
>(true);
|
|
1136
|
+
|
|
1137
|
+
expect(schema.parse({ a: 'string', b: 123, c: 123 })).toEqual({
|
|
1138
|
+
a: 'string',
|
|
1139
|
+
b: 123,
|
|
1140
|
+
c: 123,
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
expect(() => schema.parse({ a: 'string', b: 123 })).toThrow('Required');
|
|
1144
|
+
});
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
describe('union', () => {
|
|
1148
|
+
it('should parse union', () => {
|
|
1149
|
+
const schema = columnToZod(
|
|
1150
|
+
t.json((t) => t.union([t.number(), t.string()])),
|
|
1151
|
+
);
|
|
1152
|
+
|
|
1153
|
+
assertType<typeof schema, z.ZodUnion<[z.ZodNumber, z.ZodString]>>(true);
|
|
1154
|
+
|
|
1155
|
+
expect(schema.parse(123)).toBe(123);
|
|
1156
|
+
expect(schema.parse('string')).toBe('string');
|
|
1157
|
+
|
|
1158
|
+
expect(() => schema.parse(true)).toThrow('Invalid input');
|
|
1159
|
+
});
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
describe('discriminatedUnion', () => {
|
|
1163
|
+
it('should parse discriminated union', () => {
|
|
1164
|
+
const schema = columnToZod(
|
|
1165
|
+
t.json((t) =>
|
|
1166
|
+
t.discriminatedUnion('type', [
|
|
1167
|
+
t.object({ type: t.literal('a'), a: t.string() }),
|
|
1168
|
+
t.object({ type: t.literal('b'), b: t.number() }),
|
|
1169
|
+
]),
|
|
1170
|
+
),
|
|
1171
|
+
);
|
|
1172
|
+
|
|
1173
|
+
assertType<
|
|
1174
|
+
typeof schema,
|
|
1175
|
+
z.ZodDiscriminatedUnion<
|
|
1176
|
+
'type',
|
|
1177
|
+
z.Primitive,
|
|
1178
|
+
| z.ZodObject<{ type: z.ZodLiteral<'a'>; a: z.ZodString }>
|
|
1179
|
+
| z.ZodObject<{ type: z.ZodLiteral<'b'>; b: z.ZodNumber }>
|
|
1180
|
+
>
|
|
1181
|
+
>(true);
|
|
1182
|
+
|
|
1183
|
+
expect(schema.parse({ type: 'a', a: 'string' })).toEqual({
|
|
1184
|
+
type: 'a',
|
|
1185
|
+
a: 'string',
|
|
1186
|
+
});
|
|
1187
|
+
expect(schema.parse({ type: 'b', b: 123 })).toEqual({
|
|
1188
|
+
type: 'b',
|
|
1189
|
+
b: 123,
|
|
1190
|
+
});
|
|
1191
|
+
|
|
1192
|
+
expect(() => schema.parse({ type: 'c' })).toThrow(
|
|
1193
|
+
'Invalid discriminator value',
|
|
1194
|
+
);
|
|
1195
|
+
});
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
describe('lazy', () => {
|
|
1199
|
+
it('should parse lazy type', () => {
|
|
1200
|
+
interface Category {
|
|
1201
|
+
name: string;
|
|
1202
|
+
subCategories: Category[];
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
const JsonCategory: JSONType<Category> = jsonTypes.lazy(() =>
|
|
1206
|
+
jsonTypes.object({
|
|
1207
|
+
name: jsonTypes.string(),
|
|
1208
|
+
subCategories: jsonTypes.array(JsonCategory),
|
|
1209
|
+
}),
|
|
1210
|
+
);
|
|
1211
|
+
|
|
1212
|
+
const schema = columnToZod(t.json(() => JsonCategory));
|
|
1213
|
+
|
|
1214
|
+
const valid = {
|
|
1215
|
+
name: 'name',
|
|
1216
|
+
subCategories: [{ name: 'name', subCategories: [] }],
|
|
1217
|
+
};
|
|
1218
|
+
expect(schema.parse(valid)).toEqual(valid);
|
|
1219
|
+
|
|
1220
|
+
expect(() =>
|
|
1221
|
+
schema.parse({ name: 'name', subCategories: [{ name: 'name' }] }),
|
|
1222
|
+
).toThrow('Required');
|
|
1223
|
+
});
|
|
1224
|
+
});
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1227
|
+
describe('as', () => {
|
|
1228
|
+
it('should convert one type to the same schema as another type', () => {
|
|
1229
|
+
const timestampAsInteger = columnToZod(
|
|
1230
|
+
t
|
|
1231
|
+
.timestamp()
|
|
1232
|
+
.encode((input: number) => new Date(input))
|
|
1233
|
+
.parse(Date.parse)
|
|
1234
|
+
.as(t.integer()),
|
|
1235
|
+
);
|
|
1236
|
+
|
|
1237
|
+
assertType<typeof timestampAsInteger, z.ZodNumber>(true);
|
|
1238
|
+
expect(timestampAsInteger.parse(123)).toBe(123);
|
|
1239
|
+
|
|
1240
|
+
const timestampAsDate = columnToZod(
|
|
1241
|
+
t.timestamp().parse((string) => new Date(string)),
|
|
1242
|
+
);
|
|
1243
|
+
|
|
1244
|
+
assertType<typeof timestampAsDate, z.ZodDate>(true);
|
|
1245
|
+
const date = new Date();
|
|
1246
|
+
expect(timestampAsDate.parse(date)).toEqual(date);
|
|
1247
|
+
});
|
|
1248
|
+
});
|
|
1249
|
+
});
|