zod 4.2.0-canary.20250825T230433 → 4.2.0-canary.20250826T000512
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/package.json +1 -1
- package/src/v4/classic/tests/to-json-schema.test.ts +108 -9
- package/src/v4/core/to-json-schema.ts +14 -4
- package/src/v4/core/versions.ts +1 -1
- package/v4/core/to-json-schema.cjs +11 -4
- package/v4/core/to-json-schema.js +11 -4
- package/v4/core/versions.cjs +1 -1
- package/v4/core/versions.js +1 -1
package/package.json
CHANGED
|
@@ -695,6 +695,51 @@ describe("toJSONSchema", () => {
|
|
|
695
695
|
const schema = z.tuple([z.string(), z.number()]);
|
|
696
696
|
expect(z.toJSONSchema(schema, { target: "openapi-3.0" })).toMatchInlineSnapshot(`
|
|
697
697
|
{
|
|
698
|
+
"items": {
|
|
699
|
+
"anyOf": [
|
|
700
|
+
{
|
|
701
|
+
"type": "string",
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
"type": "number",
|
|
705
|
+
},
|
|
706
|
+
],
|
|
707
|
+
},
|
|
708
|
+
"maxItems": 2,
|
|
709
|
+
"minItems": 2,
|
|
710
|
+
"type": "array",
|
|
711
|
+
}
|
|
712
|
+
`);
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
test("tuple with rest openapi", () => {
|
|
716
|
+
const schema = z.tuple([z.string(), z.number()]).rest(z.boolean());
|
|
717
|
+
expect(z.toJSONSchema(schema, { target: "openapi-3.0" })).toMatchInlineSnapshot(`
|
|
718
|
+
{
|
|
719
|
+
"items": {
|
|
720
|
+
"anyOf": [
|
|
721
|
+
{
|
|
722
|
+
"type": "string",
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
"type": "number",
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
"type": "boolean",
|
|
729
|
+
},
|
|
730
|
+
],
|
|
731
|
+
},
|
|
732
|
+
"minItems": 2,
|
|
733
|
+
"type": "array",
|
|
734
|
+
}
|
|
735
|
+
`);
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
test("tuple draft-7", () => {
|
|
739
|
+
const schema = z.tuple([z.string(), z.number()]);
|
|
740
|
+
expect(z.toJSONSchema(schema, { target: "draft-7", io: "input" })).toMatchInlineSnapshot(`
|
|
741
|
+
{
|
|
742
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
698
743
|
"items": [
|
|
699
744
|
{
|
|
700
745
|
"type": "string",
|
|
@@ -703,17 +748,19 @@ describe("toJSONSchema", () => {
|
|
|
703
748
|
"type": "number",
|
|
704
749
|
},
|
|
705
750
|
],
|
|
706
|
-
"maxItems": 2,
|
|
707
|
-
"minItems": 2,
|
|
708
751
|
"type": "array",
|
|
709
752
|
}
|
|
710
753
|
`);
|
|
711
754
|
});
|
|
712
755
|
|
|
713
|
-
test("tuple with rest
|
|
756
|
+
test("tuple with rest draft-7", () => {
|
|
714
757
|
const schema = z.tuple([z.string(), z.number()]).rest(z.boolean());
|
|
715
|
-
expect(z.toJSONSchema(schema, { target: "
|
|
758
|
+
expect(z.toJSONSchema(schema, { target: "draft-7", io: "input" })).toMatchInlineSnapshot(`
|
|
716
759
|
{
|
|
760
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
761
|
+
"additionalItems": {
|
|
762
|
+
"type": "boolean",
|
|
763
|
+
},
|
|
717
764
|
"items": [
|
|
718
765
|
{
|
|
719
766
|
"type": "string",
|
|
@@ -721,14 +768,60 @@ describe("toJSONSchema", () => {
|
|
|
721
768
|
{
|
|
722
769
|
"type": "number",
|
|
723
770
|
},
|
|
771
|
+
],
|
|
772
|
+
"type": "array",
|
|
773
|
+
}
|
|
774
|
+
`);
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
test("tuple with rest draft-7 - issue #5151 regression test", () => {
|
|
778
|
+
// This test addresses issue #5151: tuple with rest elements and ids
|
|
779
|
+
// in draft-7 had incorrect internal path handling affecting complex scenarios
|
|
780
|
+
const primarySchema = z.string().meta({ id: "primary" });
|
|
781
|
+
const restSchema = z.number().meta({ id: "rest" });
|
|
782
|
+
const testSchema = z.tuple([primarySchema], restSchema);
|
|
783
|
+
|
|
784
|
+
// Test both final output structure AND internal path handling
|
|
785
|
+
const capturedPaths: string[] = [];
|
|
786
|
+
const result = z.toJSONSchema(testSchema, {
|
|
787
|
+
target: "draft-7",
|
|
788
|
+
override: (ctx) => capturedPaths.push(ctx.path.join("/")),
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
// Verify correct draft-7 structure with metadata extraction
|
|
792
|
+
expect(result).toMatchInlineSnapshot(`
|
|
793
|
+
{
|
|
794
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
795
|
+
"additionalItems": {
|
|
796
|
+
"$ref": "#/definitions/rest",
|
|
797
|
+
},
|
|
798
|
+
"definitions": {
|
|
799
|
+
"primary": {
|
|
800
|
+
"id": "primary",
|
|
801
|
+
"type": "string",
|
|
802
|
+
},
|
|
803
|
+
"rest": {
|
|
804
|
+
"id": "rest",
|
|
805
|
+
"type": "number",
|
|
806
|
+
},
|
|
807
|
+
},
|
|
808
|
+
"items": [
|
|
724
809
|
{
|
|
725
|
-
"
|
|
810
|
+
"$ref": "#/definitions/primary",
|
|
726
811
|
},
|
|
727
812
|
],
|
|
728
|
-
"minItems": 2,
|
|
729
813
|
"type": "array",
|
|
730
814
|
}
|
|
731
815
|
`);
|
|
816
|
+
|
|
817
|
+
// Verify internal paths are correct (this was the actual bug)
|
|
818
|
+
expect(capturedPaths).toContain("items/0"); // prefix items should use "items" path
|
|
819
|
+
expect(capturedPaths).toContain("additionalItems"); // rest should use "additionalItems" path
|
|
820
|
+
expect(capturedPaths).not.toContain("prefixItems/0"); // should not use draft-2020-12 paths
|
|
821
|
+
|
|
822
|
+
// Structural validations
|
|
823
|
+
expect(Array.isArray(result.items)).toBe(true);
|
|
824
|
+
expect(result.additionalItems).toBeDefined();
|
|
732
825
|
});
|
|
733
826
|
|
|
734
827
|
test("promise", () => {
|
|
@@ -1554,7 +1647,9 @@ test("unrepresentable literal values are ignored", () => {
|
|
|
1554
1647
|
}
|
|
1555
1648
|
`);
|
|
1556
1649
|
|
|
1557
|
-
const b = z.z.toJSONSchema(z.literal([undefined, null, 5, BigInt(1324)]), {
|
|
1650
|
+
const b = z.z.toJSONSchema(z.literal([undefined, null, 5, BigInt(1324)]), {
|
|
1651
|
+
unrepresentable: "any",
|
|
1652
|
+
});
|
|
1558
1653
|
expect(b).toMatchInlineSnapshot(`
|
|
1559
1654
|
{
|
|
1560
1655
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -1566,7 +1661,9 @@ test("unrepresentable literal values are ignored", () => {
|
|
|
1566
1661
|
}
|
|
1567
1662
|
`);
|
|
1568
1663
|
|
|
1569
|
-
const c = z.z.toJSONSchema(z.literal([undefined]), {
|
|
1664
|
+
const c = z.z.toJSONSchema(z.literal([undefined]), {
|
|
1665
|
+
unrepresentable: "any",
|
|
1666
|
+
});
|
|
1570
1667
|
expect(c).toMatchInlineSnapshot(`
|
|
1571
1668
|
{
|
|
1572
1669
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -1849,7 +1946,9 @@ test("basic registry", () => {
|
|
|
1849
1946
|
myRegistry.add(User, { id: "User" });
|
|
1850
1947
|
myRegistry.add(Post, { id: "Post" });
|
|
1851
1948
|
|
|
1852
|
-
const result = z.z.toJSONSchema(myRegistry, {
|
|
1949
|
+
const result = z.z.toJSONSchema(myRegistry, {
|
|
1950
|
+
uri: (id) => `https://example.com/${id}.json`,
|
|
1951
|
+
});
|
|
1853
1952
|
expect(result).toMatchInlineSnapshot(`
|
|
1854
1953
|
{
|
|
1855
1954
|
"schemas": {
|
|
@@ -368,13 +368,21 @@ export class JSONSchemaGenerator {
|
|
|
368
368
|
case "tuple": {
|
|
369
369
|
const json: JSONSchema.ArraySchema = _json as any;
|
|
370
370
|
json.type = "array";
|
|
371
|
+
|
|
372
|
+
const prefixPath = this.target === "draft-2020-12" ? "prefixItems" : "items";
|
|
373
|
+
const restPath =
|
|
374
|
+
this.target === "draft-2020-12" ? "items" : this.target === "openapi-3.0" ? "items" : "additionalItems";
|
|
375
|
+
|
|
371
376
|
const prefixItems = def.items.map((x, i) =>
|
|
372
|
-
this.process(x, {
|
|
377
|
+
this.process(x, {
|
|
378
|
+
...params,
|
|
379
|
+
path: [...params.path, prefixPath, i],
|
|
380
|
+
})
|
|
373
381
|
);
|
|
374
382
|
const rest = def.rest
|
|
375
383
|
? this.process(def.rest, {
|
|
376
384
|
...params,
|
|
377
|
-
path: [...params.path, "items
|
|
385
|
+
path: [...params.path, restPath, ...(this.target === "openapi-3.0" ? [def.items.length] : [])],
|
|
378
386
|
})
|
|
379
387
|
: null;
|
|
380
388
|
|
|
@@ -384,9 +392,11 @@ export class JSONSchemaGenerator {
|
|
|
384
392
|
json.items = rest;
|
|
385
393
|
}
|
|
386
394
|
} else if (this.target === "openapi-3.0") {
|
|
387
|
-
json.items =
|
|
395
|
+
json.items = {
|
|
396
|
+
anyOf: [...prefixItems],
|
|
397
|
+
};
|
|
388
398
|
if (rest) {
|
|
389
|
-
json.items.push(rest);
|
|
399
|
+
json.items.anyOf!.push(rest);
|
|
390
400
|
}
|
|
391
401
|
json.minItems = prefixItems.length;
|
|
392
402
|
if (!rest) {
|
package/src/v4/core/versions.ts
CHANGED
|
@@ -288,11 +288,16 @@ class JSONSchemaGenerator {
|
|
|
288
288
|
case "tuple": {
|
|
289
289
|
const json = _json;
|
|
290
290
|
json.type = "array";
|
|
291
|
-
const
|
|
291
|
+
const prefixPath = this.target === "draft-2020-12" ? "prefixItems" : "items";
|
|
292
|
+
const restPath = this.target === "draft-2020-12" ? "items" : this.target === "openapi-3.0" ? "items" : "additionalItems";
|
|
293
|
+
const prefixItems = def.items.map((x, i) => this.process(x, {
|
|
294
|
+
...params,
|
|
295
|
+
path: [...params.path, prefixPath, i],
|
|
296
|
+
}));
|
|
292
297
|
const rest = def.rest
|
|
293
298
|
? this.process(def.rest, {
|
|
294
299
|
...params,
|
|
295
|
-
path: [...params.path, "items
|
|
300
|
+
path: [...params.path, restPath, ...(this.target === "openapi-3.0" ? [def.items.length] : [])],
|
|
296
301
|
})
|
|
297
302
|
: null;
|
|
298
303
|
if (this.target === "draft-2020-12") {
|
|
@@ -302,9 +307,11 @@ class JSONSchemaGenerator {
|
|
|
302
307
|
}
|
|
303
308
|
}
|
|
304
309
|
else if (this.target === "openapi-3.0") {
|
|
305
|
-
json.items =
|
|
310
|
+
json.items = {
|
|
311
|
+
anyOf: [...prefixItems],
|
|
312
|
+
};
|
|
306
313
|
if (rest) {
|
|
307
|
-
json.items.push(rest);
|
|
314
|
+
json.items.anyOf.push(rest);
|
|
308
315
|
}
|
|
309
316
|
json.minItems = prefixItems.length;
|
|
310
317
|
if (!rest) {
|
|
@@ -284,11 +284,16 @@ export class JSONSchemaGenerator {
|
|
|
284
284
|
case "tuple": {
|
|
285
285
|
const json = _json;
|
|
286
286
|
json.type = "array";
|
|
287
|
-
const
|
|
287
|
+
const prefixPath = this.target === "draft-2020-12" ? "prefixItems" : "items";
|
|
288
|
+
const restPath = this.target === "draft-2020-12" ? "items" : this.target === "openapi-3.0" ? "items" : "additionalItems";
|
|
289
|
+
const prefixItems = def.items.map((x, i) => this.process(x, {
|
|
290
|
+
...params,
|
|
291
|
+
path: [...params.path, prefixPath, i],
|
|
292
|
+
}));
|
|
288
293
|
const rest = def.rest
|
|
289
294
|
? this.process(def.rest, {
|
|
290
295
|
...params,
|
|
291
|
-
path: [...params.path, "items
|
|
296
|
+
path: [...params.path, restPath, ...(this.target === "openapi-3.0" ? [def.items.length] : [])],
|
|
292
297
|
})
|
|
293
298
|
: null;
|
|
294
299
|
if (this.target === "draft-2020-12") {
|
|
@@ -298,9 +303,11 @@ export class JSONSchemaGenerator {
|
|
|
298
303
|
}
|
|
299
304
|
}
|
|
300
305
|
else if (this.target === "openapi-3.0") {
|
|
301
|
-
json.items =
|
|
306
|
+
json.items = {
|
|
307
|
+
anyOf: [...prefixItems],
|
|
308
|
+
};
|
|
302
309
|
if (rest) {
|
|
303
|
-
json.items.push(rest);
|
|
310
|
+
json.items.anyOf.push(rest);
|
|
304
311
|
}
|
|
305
312
|
json.minItems = prefixItems.length;
|
|
306
313
|
if (!rest) {
|
package/v4/core/versions.cjs
CHANGED
package/v4/core/versions.js
CHANGED