zod 4.2.0-canary.20250825T235836 → 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
CHANGED
|
@@ -735,6 +735,95 @@ describe("toJSONSchema", () => {
|
|
|
735
735
|
`);
|
|
736
736
|
});
|
|
737
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#",
|
|
743
|
+
"items": [
|
|
744
|
+
{
|
|
745
|
+
"type": "string",
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
"type": "number",
|
|
749
|
+
},
|
|
750
|
+
],
|
|
751
|
+
"type": "array",
|
|
752
|
+
}
|
|
753
|
+
`);
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
test("tuple with rest draft-7", () => {
|
|
757
|
+
const schema = z.tuple([z.string(), z.number()]).rest(z.boolean());
|
|
758
|
+
expect(z.toJSONSchema(schema, { target: "draft-7", io: "input" })).toMatchInlineSnapshot(`
|
|
759
|
+
{
|
|
760
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
761
|
+
"additionalItems": {
|
|
762
|
+
"type": "boolean",
|
|
763
|
+
},
|
|
764
|
+
"items": [
|
|
765
|
+
{
|
|
766
|
+
"type": "string",
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
"type": "number",
|
|
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": [
|
|
809
|
+
{
|
|
810
|
+
"$ref": "#/definitions/primary",
|
|
811
|
+
},
|
|
812
|
+
],
|
|
813
|
+
"type": "array",
|
|
814
|
+
}
|
|
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();
|
|
825
|
+
});
|
|
826
|
+
|
|
738
827
|
test("promise", () => {
|
|
739
828
|
const schema = z.promise(z.string());
|
|
740
829
|
expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
|
|
@@ -1558,7 +1647,9 @@ test("unrepresentable literal values are ignored", () => {
|
|
|
1558
1647
|
}
|
|
1559
1648
|
`);
|
|
1560
1649
|
|
|
1561
|
-
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
|
+
});
|
|
1562
1653
|
expect(b).toMatchInlineSnapshot(`
|
|
1563
1654
|
{
|
|
1564
1655
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -1570,7 +1661,9 @@ test("unrepresentable literal values are ignored", () => {
|
|
|
1570
1661
|
}
|
|
1571
1662
|
`);
|
|
1572
1663
|
|
|
1573
|
-
const c = z.z.toJSONSchema(z.literal([undefined]), {
|
|
1664
|
+
const c = z.z.toJSONSchema(z.literal([undefined]), {
|
|
1665
|
+
unrepresentable: "any",
|
|
1666
|
+
});
|
|
1574
1667
|
expect(c).toMatchInlineSnapshot(`
|
|
1575
1668
|
{
|
|
1576
1669
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -1853,7 +1946,9 @@ test("basic registry", () => {
|
|
|
1853
1946
|
myRegistry.add(User, { id: "User" });
|
|
1854
1947
|
myRegistry.add(Post, { id: "Post" });
|
|
1855
1948
|
|
|
1856
|
-
const result = z.z.toJSONSchema(myRegistry, {
|
|
1949
|
+
const result = z.z.toJSONSchema(myRegistry, {
|
|
1950
|
+
uri: (id) => `https://example.com/${id}.json`,
|
|
1951
|
+
});
|
|
1857
1952
|
expect(result).toMatchInlineSnapshot(`
|
|
1858
1953
|
{
|
|
1859
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
|
|
|
@@ -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") {
|
|
@@ -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") {
|