zod 4.2.0-canary.20250825T235836 → 4.2.0-canary.20250826T001136
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
|
@@ -652,6 +652,18 @@ describe("toJSONSchema", () => {
|
|
|
652
652
|
`);
|
|
653
653
|
});
|
|
654
654
|
|
|
655
|
+
test("record openapi", () => {
|
|
656
|
+
const schema = z.record(z.string(), z.boolean());
|
|
657
|
+
expect(z.toJSONSchema(schema, { target: "openapi-3.0" })).toMatchInlineSnapshot(`
|
|
658
|
+
{
|
|
659
|
+
"additionalProperties": {
|
|
660
|
+
"type": "boolean",
|
|
661
|
+
},
|
|
662
|
+
"type": "object",
|
|
663
|
+
}
|
|
664
|
+
`);
|
|
665
|
+
});
|
|
666
|
+
|
|
655
667
|
test("tuple", () => {
|
|
656
668
|
const schema = z.tuple([z.string(), z.number()]);
|
|
657
669
|
expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
|
|
@@ -735,6 +747,95 @@ describe("toJSONSchema", () => {
|
|
|
735
747
|
`);
|
|
736
748
|
});
|
|
737
749
|
|
|
750
|
+
test("tuple draft-7", () => {
|
|
751
|
+
const schema = z.tuple([z.string(), z.number()]);
|
|
752
|
+
expect(z.toJSONSchema(schema, { target: "draft-7", io: "input" })).toMatchInlineSnapshot(`
|
|
753
|
+
{
|
|
754
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
755
|
+
"items": [
|
|
756
|
+
{
|
|
757
|
+
"type": "string",
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
"type": "number",
|
|
761
|
+
},
|
|
762
|
+
],
|
|
763
|
+
"type": "array",
|
|
764
|
+
}
|
|
765
|
+
`);
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
test("tuple with rest draft-7", () => {
|
|
769
|
+
const schema = z.tuple([z.string(), z.number()]).rest(z.boolean());
|
|
770
|
+
expect(z.toJSONSchema(schema, { target: "draft-7", io: "input" })).toMatchInlineSnapshot(`
|
|
771
|
+
{
|
|
772
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
773
|
+
"additionalItems": {
|
|
774
|
+
"type": "boolean",
|
|
775
|
+
},
|
|
776
|
+
"items": [
|
|
777
|
+
{
|
|
778
|
+
"type": "string",
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
"type": "number",
|
|
782
|
+
},
|
|
783
|
+
],
|
|
784
|
+
"type": "array",
|
|
785
|
+
}
|
|
786
|
+
`);
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
test("tuple with rest draft-7 - issue #5151 regression test", () => {
|
|
790
|
+
// This test addresses issue #5151: tuple with rest elements and ids
|
|
791
|
+
// in draft-7 had incorrect internal path handling affecting complex scenarios
|
|
792
|
+
const primarySchema = z.string().meta({ id: "primary" });
|
|
793
|
+
const restSchema = z.number().meta({ id: "rest" });
|
|
794
|
+
const testSchema = z.tuple([primarySchema], restSchema);
|
|
795
|
+
|
|
796
|
+
// Test both final output structure AND internal path handling
|
|
797
|
+
const capturedPaths: string[] = [];
|
|
798
|
+
const result = z.toJSONSchema(testSchema, {
|
|
799
|
+
target: "draft-7",
|
|
800
|
+
override: (ctx) => capturedPaths.push(ctx.path.join("/")),
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
// Verify correct draft-7 structure with metadata extraction
|
|
804
|
+
expect(result).toMatchInlineSnapshot(`
|
|
805
|
+
{
|
|
806
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
807
|
+
"additionalItems": {
|
|
808
|
+
"$ref": "#/definitions/rest",
|
|
809
|
+
},
|
|
810
|
+
"definitions": {
|
|
811
|
+
"primary": {
|
|
812
|
+
"id": "primary",
|
|
813
|
+
"type": "string",
|
|
814
|
+
},
|
|
815
|
+
"rest": {
|
|
816
|
+
"id": "rest",
|
|
817
|
+
"type": "number",
|
|
818
|
+
},
|
|
819
|
+
},
|
|
820
|
+
"items": [
|
|
821
|
+
{
|
|
822
|
+
"$ref": "#/definitions/primary",
|
|
823
|
+
},
|
|
824
|
+
],
|
|
825
|
+
"type": "array",
|
|
826
|
+
}
|
|
827
|
+
`);
|
|
828
|
+
|
|
829
|
+
// Verify internal paths are correct (this was the actual bug)
|
|
830
|
+
expect(capturedPaths).toContain("items/0"); // prefix items should use "items" path
|
|
831
|
+
expect(capturedPaths).toContain("additionalItems"); // rest should use "additionalItems" path
|
|
832
|
+
expect(capturedPaths).not.toContain("prefixItems/0"); // should not use draft-2020-12 paths
|
|
833
|
+
|
|
834
|
+
// Structural validations
|
|
835
|
+
expect(Array.isArray(result.items)).toBe(true);
|
|
836
|
+
expect(result.additionalItems).toBeDefined();
|
|
837
|
+
});
|
|
838
|
+
|
|
738
839
|
test("promise", () => {
|
|
739
840
|
const schema = z.promise(z.string());
|
|
740
841
|
expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
|
|
@@ -1558,7 +1659,9 @@ test("unrepresentable literal values are ignored", () => {
|
|
|
1558
1659
|
}
|
|
1559
1660
|
`);
|
|
1560
1661
|
|
|
1561
|
-
const b = z.z.toJSONSchema(z.literal([undefined, null, 5, BigInt(1324)]), {
|
|
1662
|
+
const b = z.z.toJSONSchema(z.literal([undefined, null, 5, BigInt(1324)]), {
|
|
1663
|
+
unrepresentable: "any",
|
|
1664
|
+
});
|
|
1562
1665
|
expect(b).toMatchInlineSnapshot(`
|
|
1563
1666
|
{
|
|
1564
1667
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -1570,7 +1673,9 @@ test("unrepresentable literal values are ignored", () => {
|
|
|
1570
1673
|
}
|
|
1571
1674
|
`);
|
|
1572
1675
|
|
|
1573
|
-
const c = z.z.toJSONSchema(z.literal([undefined]), {
|
|
1676
|
+
const c = z.z.toJSONSchema(z.literal([undefined]), {
|
|
1677
|
+
unrepresentable: "any",
|
|
1678
|
+
});
|
|
1574
1679
|
expect(c).toMatchInlineSnapshot(`
|
|
1575
1680
|
{
|
|
1576
1681
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -1853,7 +1958,9 @@ test("basic registry", () => {
|
|
|
1853
1958
|
myRegistry.add(User, { id: "User" });
|
|
1854
1959
|
myRegistry.add(Post, { id: "Post" });
|
|
1855
1960
|
|
|
1856
|
-
const result = z.z.toJSONSchema(myRegistry, {
|
|
1961
|
+
const result = z.z.toJSONSchema(myRegistry, {
|
|
1962
|
+
uri: (id) => `https://example.com/${id}.json`,
|
|
1963
|
+
});
|
|
1857
1964
|
expect(result).toMatchInlineSnapshot(`
|
|
1858
1965
|
{
|
|
1859
1966
|
"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
|
|
|
@@ -413,7 +421,7 @@ export class JSONSchemaGenerator {
|
|
|
413
421
|
case "record": {
|
|
414
422
|
const json: JSONSchema.ObjectSchema = _json as any;
|
|
415
423
|
json.type = "object";
|
|
416
|
-
if (this.target
|
|
424
|
+
if (this.target === "draft-7" || this.target === "draft-2020-12") {
|
|
417
425
|
json.propertyNames = this.process(def.keyType, {
|
|
418
426
|
...params,
|
|
419
427
|
path: [...params.path, "propertyNames"],
|
|
@@ -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") {
|
|
@@ -330,7 +335,7 @@ class JSONSchemaGenerator {
|
|
|
330
335
|
case "record": {
|
|
331
336
|
const json = _json;
|
|
332
337
|
json.type = "object";
|
|
333
|
-
if (this.target
|
|
338
|
+
if (this.target === "draft-7" || this.target === "draft-2020-12") {
|
|
334
339
|
json.propertyNames = this.process(def.keyType, {
|
|
335
340
|
...params,
|
|
336
341
|
path: [...params.path, "propertyNames"],
|
|
@@ -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") {
|
|
@@ -326,7 +331,7 @@ export class JSONSchemaGenerator {
|
|
|
326
331
|
case "record": {
|
|
327
332
|
const json = _json;
|
|
328
333
|
json.type = "object";
|
|
329
|
-
if (this.target
|
|
334
|
+
if (this.target === "draft-7" || this.target === "draft-2020-12") {
|
|
330
335
|
json.propertyNames = this.process(def.keyType, {
|
|
331
336
|
...params,
|
|
332
337
|
path: [...params.path, "propertyNames"],
|