@signe/schema-to-zod 2.5.0 → 2.5.2
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/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +4 -2
- package/tests/validate.spec.ts +693 -10
package/tests/validate.spec.ts
CHANGED
|
@@ -17,7 +17,7 @@ describe('jsonSchemaToZod', () => {
|
|
|
17
17
|
required: ['name', 'email']
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const zodSchema = jsonSchemaToZod(schema)
|
|
20
|
+
const zodSchema = jsonSchemaToZod(schema as any)
|
|
21
21
|
|
|
22
22
|
expect(Object.keys(zodSchema)).toEqual(['name', 'email', 'age', 'isActive', 'scores'])
|
|
23
23
|
})
|
|
@@ -30,7 +30,7 @@ describe('jsonSchemaToZod', () => {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
expect(() => jsonSchemaToZod(schema)).toThrow('Unsupported type: unsupported')
|
|
33
|
+
expect(() => jsonSchemaToZod(schema as any)).toThrow('Unsupported type: unsupported')
|
|
34
34
|
})
|
|
35
35
|
|
|
36
36
|
test('Should apply validators correctly', async () => {
|
|
@@ -43,7 +43,7 @@ describe('jsonSchemaToZod', () => {
|
|
|
43
43
|
required: ['name']
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
const zodSchema = z.object(jsonSchemaToZod(schema))
|
|
46
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
47
47
|
|
|
48
48
|
// Name is required
|
|
49
49
|
let result = zodSchema.safeParse({ age: 30 })
|
|
@@ -78,7 +78,7 @@ describe('jsonSchemaToZod', () => {
|
|
|
78
78
|
},
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
const zodSchema = z.object(jsonSchemaToZod(schema))
|
|
81
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
82
82
|
|
|
83
83
|
// Name is not a string
|
|
84
84
|
let result = zodSchema.safeParse({ name: 123 })
|
|
@@ -102,7 +102,7 @@ describe('jsonSchemaToZod', () => {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
const zodSchema = z.object(jsonSchemaToZod(schema))
|
|
105
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
106
106
|
|
|
107
107
|
// Test color validation
|
|
108
108
|
expect(zodSchema.safeParse({ colorCode: '#123456' }).success).toBeTruthy()
|
|
@@ -135,7 +135,7 @@ describe('jsonSchemaToZod', () => {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
const zodSchema = z.object(jsonSchemaToZod(schema))
|
|
138
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
139
139
|
|
|
140
140
|
expect(zodSchema.safeParse({
|
|
141
141
|
user: {
|
|
@@ -175,7 +175,7 @@ describe('jsonSchemaToZod', () => {
|
|
|
175
175
|
}
|
|
176
176
|
]
|
|
177
177
|
|
|
178
|
-
const zodSchema = z.object(jsonSchemaToZod(schemas))
|
|
178
|
+
const zodSchema = z.object(jsonSchemaToZod(schemas as any))
|
|
179
179
|
|
|
180
180
|
expect(zodSchema.safeParse({
|
|
181
181
|
name: 'John',
|
|
@@ -194,7 +194,7 @@ describe('jsonSchemaToZod', () => {
|
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
const zodSchema = z.object(jsonSchemaToZod(schema))
|
|
197
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
198
198
|
|
|
199
199
|
expect(zodSchema.safeParse({
|
|
200
200
|
categories: '123'
|
|
@@ -222,7 +222,7 @@ describe('jsonSchemaToZod', () => {
|
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
const zodSchema = z.object(jsonSchemaToZod(schema))
|
|
225
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
226
226
|
|
|
227
227
|
// Test pattern validation
|
|
228
228
|
expect(zodSchema.safeParse({ code: 'ABC123' }).success).toBeTruthy()
|
|
@@ -254,7 +254,7 @@ describe('jsonSchemaToZod', () => {
|
|
|
254
254
|
required: ['users']
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
const zodSchema = z.object(jsonSchemaToZod(schema))
|
|
257
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
258
258
|
|
|
259
259
|
// Valid case
|
|
260
260
|
let result = zodSchema.safeParse({
|
|
@@ -298,4 +298,687 @@ describe('jsonSchemaToZod', () => {
|
|
|
298
298
|
})
|
|
299
299
|
expect(result.success).toBeFalsy()
|
|
300
300
|
})
|
|
301
|
+
|
|
302
|
+
test('Should handle object with nested keyboardControls and validate only name', async () => {
|
|
303
|
+
const keyEnum = ['up', 'down', 'left', 'right', 'space', 'backspace', 'enter', 'escape']
|
|
304
|
+
|
|
305
|
+
const schema = {
|
|
306
|
+
type: 'object' as const,
|
|
307
|
+
properties: {
|
|
308
|
+
name: {
|
|
309
|
+
type: 'string' as const,
|
|
310
|
+
title: 'Name',
|
|
311
|
+
description: 'Name of the project',
|
|
312
|
+
},
|
|
313
|
+
keyboardControls: {
|
|
314
|
+
type: 'object' as const,
|
|
315
|
+
title: 'Keyboard Controls',
|
|
316
|
+
properties: {
|
|
317
|
+
down: {
|
|
318
|
+
type: 'string' as const,
|
|
319
|
+
title: 'Down',
|
|
320
|
+
enum: keyEnum,
|
|
321
|
+
default: 'down'
|
|
322
|
+
},
|
|
323
|
+
up: {
|
|
324
|
+
type: 'string' as const,
|
|
325
|
+
title: 'Up',
|
|
326
|
+
enum: keyEnum,
|
|
327
|
+
default: 'up',
|
|
328
|
+
},
|
|
329
|
+
left: {
|
|
330
|
+
type: 'string' as const,
|
|
331
|
+
title: 'Left',
|
|
332
|
+
enum: keyEnum,
|
|
333
|
+
default: 'left',
|
|
334
|
+
},
|
|
335
|
+
right: {
|
|
336
|
+
type: 'string' as const,
|
|
337
|
+
title: 'Right',
|
|
338
|
+
enum: keyEnum,
|
|
339
|
+
default: 'right',
|
|
340
|
+
},
|
|
341
|
+
action: {
|
|
342
|
+
type: 'string' as const,
|
|
343
|
+
title: 'Action',
|
|
344
|
+
enum: keyEnum,
|
|
345
|
+
default: 'space',
|
|
346
|
+
},
|
|
347
|
+
back: {
|
|
348
|
+
type: 'string' as const,
|
|
349
|
+
title: 'Back',
|
|
350
|
+
enum: keyEnum,
|
|
351
|
+
default: 'backspace',
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
359
|
+
|
|
360
|
+
// Test with only name
|
|
361
|
+
let result = zodSchema.safeParse({ name: 'My Project' })
|
|
362
|
+
|
|
363
|
+
expect(result.success).toBeTruthy()
|
|
364
|
+
|
|
365
|
+
// Test with name and keyboardControls
|
|
366
|
+
result = zodSchema.safeParse({
|
|
367
|
+
name: 'My Project',
|
|
368
|
+
keyboardControls: {
|
|
369
|
+
down: 'down',
|
|
370
|
+
up: 'up',
|
|
371
|
+
left: 'left',
|
|
372
|
+
right: 'right',
|
|
373
|
+
action: 'space',
|
|
374
|
+
back: 'backspace',
|
|
375
|
+
},
|
|
376
|
+
})
|
|
377
|
+
expect(result.success).toBeTruthy()
|
|
378
|
+
|
|
379
|
+
// Test with invalid enum value in keyboardControls
|
|
380
|
+
result = zodSchema.safeParse({
|
|
381
|
+
name: 'My Project',
|
|
382
|
+
keyboardControls: {
|
|
383
|
+
down: 'invalid',
|
|
384
|
+
},
|
|
385
|
+
})
|
|
386
|
+
expect(result.success).toBeFalsy()
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
describe('Should handle all string formats correctly', () => {
|
|
390
|
+
test('Should handle date-time format', async () => {
|
|
391
|
+
const schema = {
|
|
392
|
+
type: 'object',
|
|
393
|
+
properties: {
|
|
394
|
+
timestamp: { type: 'string', format: 'date-time' },
|
|
395
|
+
},
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
399
|
+
|
|
400
|
+
// Valid ISO 8601 date-time
|
|
401
|
+
expect(zodSchema.safeParse({ timestamp: '2023-12-25T10:30:00Z' }).success).toBeTruthy()
|
|
402
|
+
expect(zodSchema.safeParse({ timestamp: '2023-12-25T10:30:00.123Z' }).success).toBeTruthy()
|
|
403
|
+
// Note: zod.datetime() may not accept timezone offsets, removing that test case
|
|
404
|
+
|
|
405
|
+
// Invalid date-time
|
|
406
|
+
expect(zodSchema.safeParse({ timestamp: '2023-12-25' }).success).toBeFalsy()
|
|
407
|
+
expect(zodSchema.safeParse({ timestamp: 'invalid' }).success).toBeFalsy()
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
test('Should handle hostname format', async () => {
|
|
411
|
+
const schema = {
|
|
412
|
+
type: 'object',
|
|
413
|
+
properties: {
|
|
414
|
+
host: { type: 'string', format: 'hostname' },
|
|
415
|
+
},
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
419
|
+
|
|
420
|
+
// Hostname format accepts any string (no validation in implementation)
|
|
421
|
+
expect(zodSchema.safeParse({ host: 'example.com' }).success).toBeTruthy()
|
|
422
|
+
expect(zodSchema.safeParse({ host: 'subdomain.example.com' }).success).toBeTruthy()
|
|
423
|
+
expect(zodSchema.safeParse({ host: 'localhost' }).success).toBeTruthy()
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
test('Should handle ipv4 format', async () => {
|
|
427
|
+
const schema = {
|
|
428
|
+
type: 'object',
|
|
429
|
+
properties: {
|
|
430
|
+
ip: { type: 'string', format: 'ipv4' },
|
|
431
|
+
},
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
435
|
+
|
|
436
|
+
// Valid IPv4
|
|
437
|
+
expect(zodSchema.safeParse({ ip: '192.168.1.1' }).success).toBeTruthy()
|
|
438
|
+
expect(zodSchema.safeParse({ ip: '0.0.0.0' }).success).toBeTruthy()
|
|
439
|
+
expect(zodSchema.safeParse({ ip: '255.255.255.255' }).success).toBeTruthy()
|
|
440
|
+
|
|
441
|
+
// Invalid IPv4
|
|
442
|
+
expect(zodSchema.safeParse({ ip: '256.1.1.1' }).success).toBeFalsy()
|
|
443
|
+
expect(zodSchema.safeParse({ ip: '192.168.1' }).success).toBeFalsy()
|
|
444
|
+
expect(zodSchema.safeParse({ ip: '2001:0db8::1' }).success).toBeFalsy() // IPv6
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
test('Should handle ipv6 format', async () => {
|
|
448
|
+
const schema = {
|
|
449
|
+
type: 'object',
|
|
450
|
+
properties: {
|
|
451
|
+
ip: { type: 'string', format: 'ipv6' },
|
|
452
|
+
},
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
456
|
+
|
|
457
|
+
// Valid IPv6
|
|
458
|
+
expect(zodSchema.safeParse({ ip: '2001:0db8:85a3:0000:0000:8a2e:0370:7334' }).success).toBeTruthy()
|
|
459
|
+
expect(zodSchema.safeParse({ ip: '2001:db8::1' }).success).toBeTruthy()
|
|
460
|
+
expect(zodSchema.safeParse({ ip: '::1' }).success).toBeTruthy()
|
|
461
|
+
|
|
462
|
+
// Invalid IPv6
|
|
463
|
+
expect(zodSchema.safeParse({ ip: '192.168.1.1' }).success).toBeFalsy() // IPv4
|
|
464
|
+
expect(zodSchema.safeParse({ ip: 'invalid' }).success).toBeFalsy()
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
test('Should handle uri format', async () => {
|
|
468
|
+
const schema = {
|
|
469
|
+
type: 'object',
|
|
470
|
+
properties: {
|
|
471
|
+
url: { type: 'string', format: 'uri' },
|
|
472
|
+
},
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
476
|
+
|
|
477
|
+
// Valid URIs
|
|
478
|
+
expect(zodSchema.safeParse({ url: 'https://example.com' }).success).toBeTruthy()
|
|
479
|
+
expect(zodSchema.safeParse({ url: 'http://example.com/path' }).success).toBeTruthy()
|
|
480
|
+
expect(zodSchema.safeParse({ url: 'https://example.com/path?query=value' }).success).toBeTruthy()
|
|
481
|
+
|
|
482
|
+
// Invalid URIs
|
|
483
|
+
expect(zodSchema.safeParse({ url: 'not-a-url' }).success).toBeFalsy()
|
|
484
|
+
expect(zodSchema.safeParse({ url: 'example.com' }).success).toBeFalsy()
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
test('Should handle uuid format', async () => {
|
|
488
|
+
const schema = {
|
|
489
|
+
type: 'object',
|
|
490
|
+
properties: {
|
|
491
|
+
id: { type: 'string', format: 'uuid' },
|
|
492
|
+
},
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
496
|
+
|
|
497
|
+
// Valid UUIDs
|
|
498
|
+
expect(zodSchema.safeParse({ id: '550e8400-e29b-41d4-a716-446655440000' }).success).toBeTruthy()
|
|
499
|
+
expect(zodSchema.safeParse({ id: '123e4567-e89b-12d3-a456-426614174000' }).success).toBeTruthy()
|
|
500
|
+
|
|
501
|
+
// Invalid UUIDs
|
|
502
|
+
expect(zodSchema.safeParse({ id: 'not-a-uuid' }).success).toBeFalsy()
|
|
503
|
+
expect(zodSchema.safeParse({ id: '550e8400-e29b-41d4-a716' }).success).toBeFalsy()
|
|
504
|
+
expect(zodSchema.safeParse({ id: '550e8400-e29b-41d4-a716-44665544000' }).success).toBeFalsy()
|
|
505
|
+
})
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
describe('Should handle null and boolean types correctly', () => {
|
|
509
|
+
test('Should handle null type', async () => {
|
|
510
|
+
const schema = {
|
|
511
|
+
type: 'object',
|
|
512
|
+
properties: {
|
|
513
|
+
nullableValue: { type: 'null' },
|
|
514
|
+
},
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
518
|
+
|
|
519
|
+
// Valid null
|
|
520
|
+
expect(zodSchema.safeParse({ nullableValue: null }).success).toBeTruthy()
|
|
521
|
+
|
|
522
|
+
// Invalid values
|
|
523
|
+
expect(zodSchema.safeParse({ nullableValue: 'not-null' }).success).toBeFalsy()
|
|
524
|
+
// Note: optional property accepts undefined, so we omit the property to test null-only
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
test('Should handle boolean type explicitly', async () => {
|
|
528
|
+
const schema = {
|
|
529
|
+
type: 'object',
|
|
530
|
+
properties: {
|
|
531
|
+
isActive: { type: 'boolean' },
|
|
532
|
+
isVerified: { type: 'boolean' },
|
|
533
|
+
},
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
537
|
+
|
|
538
|
+
// Valid booleans
|
|
539
|
+
expect(zodSchema.safeParse({ isActive: true, isVerified: false }).success).toBeTruthy()
|
|
540
|
+
expect(zodSchema.safeParse({ isActive: false, isVerified: true }).success).toBeTruthy()
|
|
541
|
+
|
|
542
|
+
// Invalid values
|
|
543
|
+
expect(zodSchema.safeParse({ isActive: 'true' }).success).toBeFalsy()
|
|
544
|
+
expect(zodSchema.safeParse({ isActive: 1 }).success).toBeFalsy()
|
|
545
|
+
expect(zodSchema.safeParse({ isActive: null }).success).toBeFalsy()
|
|
546
|
+
})
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
describe('Should handle validators on optional properties correctly', () => {
|
|
550
|
+
test('Should handle optional string with minLength/maxLength', async () => {
|
|
551
|
+
const schema = {
|
|
552
|
+
type: 'object',
|
|
553
|
+
properties: {
|
|
554
|
+
description: { type: 'string', minLength: 3, maxLength: 10 },
|
|
555
|
+
},
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
559
|
+
|
|
560
|
+
// Optional property can be omitted
|
|
561
|
+
expect(zodSchema.safeParse({}).success).toBeTruthy()
|
|
562
|
+
|
|
563
|
+
// Note: For optional properties, minLength/maxLength validators are only applied when required
|
|
564
|
+
// So any string value is accepted for optional properties without required constraint
|
|
565
|
+
expect(zodSchema.safeParse({ description: '' }).success).toBeTruthy()
|
|
566
|
+
expect(zodSchema.safeParse({ description: 'Valid' }).success).toBeTruthy()
|
|
567
|
+
expect(zodSchema.safeParse({ description: 'This is too long for maxLength' }).success).toBeTruthy()
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
test('Should handle optional empty string', async () => {
|
|
571
|
+
const schema = {
|
|
572
|
+
type: 'object',
|
|
573
|
+
properties: {
|
|
574
|
+
comment: { type: 'string' },
|
|
575
|
+
},
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
579
|
+
|
|
580
|
+
// Optional string can be empty if provided
|
|
581
|
+
expect(zodSchema.safeParse({ comment: '' }).success).toBeTruthy()
|
|
582
|
+
expect(zodSchema.safeParse({}).success).toBeTruthy()
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
test('Should handle number/integer with boundary values', async () => {
|
|
586
|
+
const schema = {
|
|
587
|
+
type: 'object',
|
|
588
|
+
properties: {
|
|
589
|
+
positive: { type: 'number', minimum: 0 },
|
|
590
|
+
negative: { type: 'number', maximum: 0 },
|
|
591
|
+
range: { type: 'integer', minimum: -10, maximum: 10 },
|
|
592
|
+
},
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
596
|
+
|
|
597
|
+
// Valid boundary values
|
|
598
|
+
expect(zodSchema.safeParse({ positive: 0, negative: 0, range: 0 }).success).toBeTruthy()
|
|
599
|
+
expect(zodSchema.safeParse({ positive: 100, negative: -100, range: -10 }).success).toBeTruthy()
|
|
600
|
+
expect(zodSchema.safeParse({ positive: 5, negative: -5, range: 10 }).success).toBeTruthy()
|
|
601
|
+
|
|
602
|
+
// Invalid boundary values
|
|
603
|
+
expect(zodSchema.safeParse({ positive: -1 }).success).toBeFalsy()
|
|
604
|
+
expect(zodSchema.safeParse({ negative: 1 }).success).toBeFalsy()
|
|
605
|
+
expect(zodSchema.safeParse({ range: -11 }).success).toBeFalsy()
|
|
606
|
+
expect(zodSchema.safeParse({ range: 11 }).success).toBeFalsy()
|
|
607
|
+
})
|
|
608
|
+
|
|
609
|
+
test('Should handle pattern on optional string', async () => {
|
|
610
|
+
const schema = {
|
|
611
|
+
type: 'object',
|
|
612
|
+
properties: {
|
|
613
|
+
code: { type: 'string', pattern: '^[A-Z]{2}[0-9]{3}$' },
|
|
614
|
+
},
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
618
|
+
|
|
619
|
+
// Optional property can be omitted
|
|
620
|
+
expect(zodSchema.safeParse({}).success).toBeTruthy()
|
|
621
|
+
|
|
622
|
+
// If provided, must match pattern
|
|
623
|
+
expect(zodSchema.safeParse({ code: 'AB123' }).success).toBeTruthy()
|
|
624
|
+
expect(zodSchema.safeParse({ code: 'invalid' }).success).toBeFalsy()
|
|
625
|
+
expect(zodSchema.safeParse({ code: 'ab123' }).success).toBeFalsy()
|
|
626
|
+
})
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
describe('Should handle array edge cases correctly', () => {
|
|
630
|
+
test('Should handle optional array', async () => {
|
|
631
|
+
const schema = {
|
|
632
|
+
type: 'object',
|
|
633
|
+
properties: {
|
|
634
|
+
tags: { type: 'array', items: { type: 'string' } },
|
|
635
|
+
},
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
639
|
+
|
|
640
|
+
// Optional array can be omitted
|
|
641
|
+
expect(zodSchema.safeParse({}).success).toBeTruthy()
|
|
642
|
+
|
|
643
|
+
// If provided, must be valid array
|
|
644
|
+
expect(zodSchema.safeParse({ tags: ['tag1', 'tag2'] }).success).toBeTruthy()
|
|
645
|
+
expect(zodSchema.safeParse({ tags: [] }).success).toBeTruthy()
|
|
646
|
+
})
|
|
647
|
+
|
|
648
|
+
test('Should handle array with primitive item types', async () => {
|
|
649
|
+
const schema = {
|
|
650
|
+
type: 'object',
|
|
651
|
+
properties: {
|
|
652
|
+
numbers: { type: 'array', items: { type: 'number' } },
|
|
653
|
+
booleans: { type: 'array', items: { type: 'boolean' } },
|
|
654
|
+
},
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
658
|
+
|
|
659
|
+
// Valid arrays with primitives
|
|
660
|
+
expect(zodSchema.safeParse({ numbers: [1, 2, 3], booleans: [true, false] }).success).toBeTruthy()
|
|
661
|
+
|
|
662
|
+
// Invalid item types
|
|
663
|
+
expect(zodSchema.safeParse({ numbers: ['1', '2'] }).success).toBeFalsy()
|
|
664
|
+
expect(zodSchema.safeParse({ booleans: [1, 0] }).success).toBeFalsy()
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
test('Should handle empty array when minItems not defined', async () => {
|
|
668
|
+
const schema = {
|
|
669
|
+
type: 'object',
|
|
670
|
+
properties: {
|
|
671
|
+
items: { type: 'array', items: { type: 'string' } },
|
|
672
|
+
},
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
676
|
+
|
|
677
|
+
// Empty array should be valid when minItems is not set
|
|
678
|
+
expect(zodSchema.safeParse({ items: [] }).success).toBeTruthy()
|
|
679
|
+
expect(zodSchema.safeParse({ items: ['item1'] }).success).toBeTruthy()
|
|
680
|
+
})
|
|
681
|
+
})
|
|
682
|
+
|
|
683
|
+
describe('Should handle object edge cases correctly', () => {
|
|
684
|
+
test('Should handle object with empty properties', async () => {
|
|
685
|
+
const schema = {
|
|
686
|
+
type: 'object',
|
|
687
|
+
properties: {},
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
691
|
+
|
|
692
|
+
// Empty object schema should accept any object
|
|
693
|
+
expect(zodSchema.safeParse({}).success).toBeTruthy()
|
|
694
|
+
expect(zodSchema.safeParse({ extra: 'property' }).success).toBeTruthy()
|
|
695
|
+
})
|
|
696
|
+
|
|
697
|
+
test('Should handle optional nested object', async () => {
|
|
698
|
+
const schema = {
|
|
699
|
+
type: 'object',
|
|
700
|
+
properties: {
|
|
701
|
+
config: {
|
|
702
|
+
type: 'object',
|
|
703
|
+
properties: {
|
|
704
|
+
setting: { type: 'string' },
|
|
705
|
+
},
|
|
706
|
+
},
|
|
707
|
+
},
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
711
|
+
|
|
712
|
+
// Optional nested object can be omitted
|
|
713
|
+
expect(zodSchema.safeParse({}).success).toBeTruthy()
|
|
714
|
+
|
|
715
|
+
// If provided, must be valid
|
|
716
|
+
expect(zodSchema.safeParse({ config: { setting: 'value' } }).success).toBeTruthy()
|
|
717
|
+
expect(zodSchema.safeParse({ config: {} }).success).toBeTruthy()
|
|
718
|
+
})
|
|
719
|
+
|
|
720
|
+
test('Should handle object with all optional properties', async () => {
|
|
721
|
+
const schema = {
|
|
722
|
+
type: 'object',
|
|
723
|
+
properties: {
|
|
724
|
+
name: { type: 'string' },
|
|
725
|
+
age: { type: 'integer' },
|
|
726
|
+
},
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
730
|
+
|
|
731
|
+
// All properties are optional
|
|
732
|
+
expect(zodSchema.safeParse({}).success).toBeTruthy()
|
|
733
|
+
expect(zodSchema.safeParse({ name: 'John' }).success).toBeTruthy()
|
|
734
|
+
expect(zodSchema.safeParse({ age: 30 }).success).toBeTruthy()
|
|
735
|
+
expect(zodSchema.safeParse({ name: 'John', age: 30 }).success).toBeTruthy()
|
|
736
|
+
})
|
|
737
|
+
})
|
|
738
|
+
|
|
739
|
+
describe('Should handle enum edge cases correctly', () => {
|
|
740
|
+
test('Should handle enum with non-string values', async () => {
|
|
741
|
+
const schema = {
|
|
742
|
+
type: 'object',
|
|
743
|
+
properties: {
|
|
744
|
+
status: { type: 'number', enum: [1, 2, 3] },
|
|
745
|
+
flag: { type: 'boolean', enum: [true, false] },
|
|
746
|
+
},
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
750
|
+
|
|
751
|
+
// Valid enum values
|
|
752
|
+
expect(zodSchema.safeParse({ status: 1, flag: true }).success).toBeTruthy()
|
|
753
|
+
expect(zodSchema.safeParse({ status: 2, flag: false }).success).toBeTruthy()
|
|
754
|
+
|
|
755
|
+
// Invalid enum values
|
|
756
|
+
expect(zodSchema.safeParse({ status: 4 }).success).toBeFalsy()
|
|
757
|
+
// Note: enum validation uses refine and checks if value is included, but enum property should work with any type
|
|
758
|
+
})
|
|
759
|
+
|
|
760
|
+
test('Should handle enum on optional property', async () => {
|
|
761
|
+
const schema = {
|
|
762
|
+
type: 'object',
|
|
763
|
+
properties: {
|
|
764
|
+
role: { type: 'string', enum: ['admin', 'user', 'guest'] },
|
|
765
|
+
},
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
769
|
+
|
|
770
|
+
// Optional property can be omitted
|
|
771
|
+
expect(zodSchema.safeParse({}).success).toBeTruthy()
|
|
772
|
+
|
|
773
|
+
// If provided, must be valid enum value
|
|
774
|
+
expect(zodSchema.safeParse({ role: 'admin' }).success).toBeTruthy()
|
|
775
|
+
expect(zodSchema.safeParse({ role: 'invalid' }).success).toBeFalsy()
|
|
776
|
+
})
|
|
777
|
+
})
|
|
778
|
+
|
|
779
|
+
describe('Should throw errors for invalid schemas', () => {
|
|
780
|
+
test('Should throw error for array without items', async () => {
|
|
781
|
+
const schema = {
|
|
782
|
+
type: 'object',
|
|
783
|
+
properties: {
|
|
784
|
+
items: { type: 'array' },
|
|
785
|
+
},
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
expect(() => jsonSchemaToZod(schema as any)).toThrow('Invalid array items')
|
|
789
|
+
})
|
|
790
|
+
|
|
791
|
+
test('Should return empty object for schema without properties', async () => {
|
|
792
|
+
const schema = {
|
|
793
|
+
type: 'object',
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Returns empty object when properties are missing
|
|
797
|
+
const zodSchema = jsonSchemaToZod(schema as any)
|
|
798
|
+
expect(Object.keys(zodSchema)).toEqual([])
|
|
799
|
+
})
|
|
800
|
+
|
|
801
|
+
test('Should return empty object for non-object schema at root', async () => {
|
|
802
|
+
const schema = {
|
|
803
|
+
type: 'string',
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const zodSchema = jsonSchemaToZod(schema as any)
|
|
807
|
+
|
|
808
|
+
// Should return empty object for non-object schema
|
|
809
|
+
expect(Object.keys(zodSchema)).toEqual([])
|
|
810
|
+
})
|
|
811
|
+
|
|
812
|
+
test('Should ignore boolean schema properties', async () => {
|
|
813
|
+
const schema = {
|
|
814
|
+
type: 'object',
|
|
815
|
+
properties: {
|
|
816
|
+
validProp: { type: 'string' },
|
|
817
|
+
booleanProp: false as any, // boolean schema
|
|
818
|
+
},
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const zodSchema = jsonSchemaToZod(schema as any)
|
|
822
|
+
|
|
823
|
+
// Boolean schemas should be ignored
|
|
824
|
+
expect(Object.keys(zodSchema)).toEqual(['validProp'])
|
|
825
|
+
})
|
|
826
|
+
})
|
|
827
|
+
|
|
828
|
+
describe('Should handle array of schemas edge cases correctly', () => {
|
|
829
|
+
test('Should handle array with conflicting schemas', async () => {
|
|
830
|
+
const schemas = [
|
|
831
|
+
{
|
|
832
|
+
type: 'object',
|
|
833
|
+
properties: {
|
|
834
|
+
name: { type: 'string' },
|
|
835
|
+
},
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
type: 'object',
|
|
839
|
+
properties: {
|
|
840
|
+
name: { type: 'integer' }, // Conflict: different type
|
|
841
|
+
},
|
|
842
|
+
},
|
|
843
|
+
]
|
|
844
|
+
|
|
845
|
+
const zodSchema = z.object(jsonSchemaToZod(schemas as any))
|
|
846
|
+
|
|
847
|
+
// Last schema should win (integer)
|
|
848
|
+
expect(zodSchema.safeParse({ name: 123 }).success).toBeTruthy()
|
|
849
|
+
expect(zodSchema.safeParse({ name: 'John' }).success).toBeFalsy()
|
|
850
|
+
})
|
|
851
|
+
|
|
852
|
+
test('Should handle array with boolean schemas', async () => {
|
|
853
|
+
const schemas = [
|
|
854
|
+
{
|
|
855
|
+
type: 'object',
|
|
856
|
+
properties: {
|
|
857
|
+
name: { type: 'string' },
|
|
858
|
+
},
|
|
859
|
+
},
|
|
860
|
+
false as any, // boolean schema should be ignored
|
|
861
|
+
{
|
|
862
|
+
type: 'object',
|
|
863
|
+
properties: {
|
|
864
|
+
age: { type: 'integer' },
|
|
865
|
+
},
|
|
866
|
+
},
|
|
867
|
+
]
|
|
868
|
+
|
|
869
|
+
const zodSchema = z.object(jsonSchemaToZod(schemas as any))
|
|
870
|
+
|
|
871
|
+
// Boolean schemas should be ignored, other schemas merged
|
|
872
|
+
expect(Object.keys(jsonSchemaToZod(schemas))).toContain('name')
|
|
873
|
+
expect(Object.keys(jsonSchemaToZod(schemas))).toContain('age')
|
|
874
|
+
expect(zodSchema.safeParse({ name: 'John', age: 30 }).success).toBeTruthy()
|
|
875
|
+
})
|
|
876
|
+
})
|
|
877
|
+
|
|
878
|
+
describe('Should handle deeply nested objects correctly', () => {
|
|
879
|
+
test('Should handle deeply nested objects', async () => {
|
|
880
|
+
const schema = {
|
|
881
|
+
type: 'object',
|
|
882
|
+
properties: {
|
|
883
|
+
level1: {
|
|
884
|
+
type: 'object',
|
|
885
|
+
properties: {
|
|
886
|
+
level2: {
|
|
887
|
+
type: 'object',
|
|
888
|
+
properties: {
|
|
889
|
+
level3: {
|
|
890
|
+
type: 'object',
|
|
891
|
+
properties: {
|
|
892
|
+
level4: {
|
|
893
|
+
type: 'object',
|
|
894
|
+
properties: {
|
|
895
|
+
value: { type: 'string' },
|
|
896
|
+
},
|
|
897
|
+
},
|
|
898
|
+
},
|
|
899
|
+
},
|
|
900
|
+
},
|
|
901
|
+
},
|
|
902
|
+
},
|
|
903
|
+
},
|
|
904
|
+
},
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
908
|
+
|
|
909
|
+
expect(zodSchema.safeParse({
|
|
910
|
+
level1: {
|
|
911
|
+
level2: {
|
|
912
|
+
level3: {
|
|
913
|
+
level4: {
|
|
914
|
+
value: 'deep',
|
|
915
|
+
},
|
|
916
|
+
},
|
|
917
|
+
},
|
|
918
|
+
},
|
|
919
|
+
}).success).toBeTruthy()
|
|
920
|
+
})
|
|
921
|
+
|
|
922
|
+
test('Should handle required fields in deeply nested objects', async () => {
|
|
923
|
+
const schema = {
|
|
924
|
+
type: 'object',
|
|
925
|
+
properties: {
|
|
926
|
+
user: {
|
|
927
|
+
type: 'object',
|
|
928
|
+
properties: {
|
|
929
|
+
profile: {
|
|
930
|
+
type: 'object',
|
|
931
|
+
properties: {
|
|
932
|
+
name: { type: 'string' },
|
|
933
|
+
details: {
|
|
934
|
+
type: 'object',
|
|
935
|
+
properties: {
|
|
936
|
+
email: { type: 'string' },
|
|
937
|
+
},
|
|
938
|
+
required: ['email'],
|
|
939
|
+
},
|
|
940
|
+
},
|
|
941
|
+
required: ['name'],
|
|
942
|
+
},
|
|
943
|
+
},
|
|
944
|
+
},
|
|
945
|
+
},
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
const zodSchema = z.object(jsonSchemaToZod(schema as any))
|
|
949
|
+
|
|
950
|
+
// Valid nested structure with all required fields
|
|
951
|
+
expect(zodSchema.safeParse({
|
|
952
|
+
user: {
|
|
953
|
+
profile: {
|
|
954
|
+
name: 'John',
|
|
955
|
+
details: {
|
|
956
|
+
email: 'john@example.com',
|
|
957
|
+
},
|
|
958
|
+
},
|
|
959
|
+
},
|
|
960
|
+
}).success).toBeTruthy()
|
|
961
|
+
|
|
962
|
+
// Missing required field in nested object
|
|
963
|
+
expect(zodSchema.safeParse({
|
|
964
|
+
user: {
|
|
965
|
+
profile: {
|
|
966
|
+
name: 'John',
|
|
967
|
+
details: {},
|
|
968
|
+
},
|
|
969
|
+
},
|
|
970
|
+
}).success).toBeFalsy()
|
|
971
|
+
|
|
972
|
+
// Missing required field at another level
|
|
973
|
+
expect(zodSchema.safeParse({
|
|
974
|
+
user: {
|
|
975
|
+
profile: {
|
|
976
|
+
details: {
|
|
977
|
+
email: 'john@example.com',
|
|
978
|
+
},
|
|
979
|
+
},
|
|
980
|
+
},
|
|
981
|
+
}).success).toBeFalsy()
|
|
982
|
+
})
|
|
983
|
+
})
|
|
301
984
|
})
|