zod 4.1.2 → 4.1.4
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 +221 -17
- package/src/v4/core/to-json-schema.ts +25 -22
- package/src/v4/core/versions.ts +1 -1
- package/v4/core/to-json-schema.cjs +23 -25
- package/v4/core/to-json-schema.js +23 -25
- package/v4/core/versions.cjs +1 -1
- package/v4/core/versions.js +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,34 @@
|
|
|
1
|
+
import { Validator } from "@seriousme/openapi-schema-validator";
|
|
1
2
|
import { describe, expect, test } from "vitest";
|
|
2
3
|
import * as z from "zod/v4";
|
|
3
4
|
// import * as zCore from "zod/v4/core";
|
|
4
5
|
|
|
6
|
+
const openAPI30Validator = new Validator();
|
|
7
|
+
/** @see https://github.com/colinhacks/zod/issues/5147 */
|
|
8
|
+
const validateOpenAPI30Schema = async (zodJSONSchema: Record<string, unknown>): Promise<true> => {
|
|
9
|
+
const res = await openAPI30Validator.validate({
|
|
10
|
+
openapi: "3.0.0",
|
|
11
|
+
info: {
|
|
12
|
+
title: "SampleApi",
|
|
13
|
+
description: "Sample backend service",
|
|
14
|
+
version: "1.0.0",
|
|
15
|
+
},
|
|
16
|
+
components: { schemas: { test: zodJSONSchema } },
|
|
17
|
+
paths: {},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (!res.valid) {
|
|
21
|
+
// `console.error` should make `vitest` trow an unhandled error
|
|
22
|
+
// printing the validation messages in consoles
|
|
23
|
+
console.error(
|
|
24
|
+
`OpenAPI schema is not valid against ${openAPI30Validator.version}`,
|
|
25
|
+
JSON.stringify(res.errors, null, 2)
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return true;
|
|
30
|
+
};
|
|
31
|
+
|
|
5
32
|
describe("toJSONSchema", () => {
|
|
6
33
|
test("primitive types", () => {
|
|
7
34
|
expect(z.toJSONSchema(z.string())).toMatchInlineSnapshot(`
|
|
@@ -552,8 +579,11 @@ describe("toJSONSchema", () => {
|
|
|
552
579
|
`);
|
|
553
580
|
});
|
|
554
581
|
|
|
555
|
-
test("nullable openapi", () => {
|
|
556
|
-
|
|
582
|
+
test("nullable openapi-3.0", () => {
|
|
583
|
+
const schema = z.string().nullable();
|
|
584
|
+
const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
|
|
585
|
+
validateOpenAPI30Schema(jsonSchema);
|
|
586
|
+
expect(jsonSchema).toMatchInlineSnapshot(`
|
|
557
587
|
{
|
|
558
588
|
"nullable": true,
|
|
559
589
|
"type": "string",
|
|
@@ -561,12 +591,39 @@ describe("toJSONSchema", () => {
|
|
|
561
591
|
`);
|
|
562
592
|
});
|
|
563
593
|
|
|
564
|
-
test("union with null openapi", () => {
|
|
594
|
+
test("union with null openapi-3.0", () => {
|
|
565
595
|
const schema = z.union([z.string(), z.null()]);
|
|
566
|
-
|
|
596
|
+
const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
|
|
597
|
+
validateOpenAPI30Schema(jsonSchema);
|
|
598
|
+
expect(jsonSchema).toMatchInlineSnapshot(`
|
|
567
599
|
{
|
|
568
|
-
"
|
|
569
|
-
|
|
600
|
+
"anyOf": [
|
|
601
|
+
{
|
|
602
|
+
"type": "string",
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
"enum": [
|
|
606
|
+
null,
|
|
607
|
+
],
|
|
608
|
+
"nullable": true,
|
|
609
|
+
"type": "string",
|
|
610
|
+
},
|
|
611
|
+
],
|
|
612
|
+
}
|
|
613
|
+
`);
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test("number with exclusive min-max openapi-3.0", () => {
|
|
617
|
+
const schema = z.number().lt(100).gt(1);
|
|
618
|
+
const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
|
|
619
|
+
validateOpenAPI30Schema(jsonSchema);
|
|
620
|
+
expect(jsonSchema).toMatchInlineSnapshot(`
|
|
621
|
+
{
|
|
622
|
+
"exclusiveMaximum": true,
|
|
623
|
+
"exclusiveMinimum": true,
|
|
624
|
+
"maximum": 100,
|
|
625
|
+
"minimum": 1,
|
|
626
|
+
"type": "number",
|
|
570
627
|
}
|
|
571
628
|
`);
|
|
572
629
|
});
|
|
@@ -652,6 +709,20 @@ describe("toJSONSchema", () => {
|
|
|
652
709
|
`);
|
|
653
710
|
});
|
|
654
711
|
|
|
712
|
+
test("record openapi-3.0", () => {
|
|
713
|
+
const schema = z.record(z.string(), z.boolean());
|
|
714
|
+
const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
|
|
715
|
+
validateOpenAPI30Schema(jsonSchema);
|
|
716
|
+
expect(jsonSchema).toMatchInlineSnapshot(`
|
|
717
|
+
{
|
|
718
|
+
"additionalProperties": {
|
|
719
|
+
"type": "boolean",
|
|
720
|
+
},
|
|
721
|
+
"type": "object",
|
|
722
|
+
}
|
|
723
|
+
`);
|
|
724
|
+
});
|
|
725
|
+
|
|
655
726
|
test("tuple", () => {
|
|
656
727
|
const schema = z.tuple([z.string(), z.number()]);
|
|
657
728
|
expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
|
|
@@ -691,10 +762,89 @@ describe("toJSONSchema", () => {
|
|
|
691
762
|
`);
|
|
692
763
|
});
|
|
693
764
|
|
|
694
|
-
test("tuple openapi", () => {
|
|
765
|
+
test("tuple openapi-3.0", () => {
|
|
766
|
+
const schema = z.tuple([z.string(), z.number()]);
|
|
767
|
+
const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
|
|
768
|
+
validateOpenAPI30Schema(jsonSchema);
|
|
769
|
+
expect(jsonSchema).toMatchInlineSnapshot(`
|
|
770
|
+
{
|
|
771
|
+
"items": {
|
|
772
|
+
"anyOf": [
|
|
773
|
+
{
|
|
774
|
+
"type": "string",
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
"type": "number",
|
|
778
|
+
},
|
|
779
|
+
],
|
|
780
|
+
},
|
|
781
|
+
"maxItems": 2,
|
|
782
|
+
"minItems": 2,
|
|
783
|
+
"type": "array",
|
|
784
|
+
}
|
|
785
|
+
`);
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
test("tuple with rest openapi-3.0", () => {
|
|
789
|
+
const schema = z.tuple([z.string(), z.number()]).rest(z.boolean());
|
|
790
|
+
const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
|
|
791
|
+
validateOpenAPI30Schema(jsonSchema);
|
|
792
|
+
expect(jsonSchema).toMatchInlineSnapshot(`
|
|
793
|
+
{
|
|
794
|
+
"items": {
|
|
795
|
+
"anyOf": [
|
|
796
|
+
{
|
|
797
|
+
"type": "string",
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
"type": "number",
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
"type": "boolean",
|
|
804
|
+
},
|
|
805
|
+
],
|
|
806
|
+
},
|
|
807
|
+
"minItems": 3,
|
|
808
|
+
"type": "array",
|
|
809
|
+
}
|
|
810
|
+
`);
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
test("tuple with null openapi-3.0", () => {
|
|
814
|
+
const schema = z.tuple([z.string(), z.number(), z.null()]);
|
|
815
|
+
const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
|
|
816
|
+
validateOpenAPI30Schema(jsonSchema);
|
|
817
|
+
expect(jsonSchema).toMatchInlineSnapshot(`
|
|
818
|
+
{
|
|
819
|
+
"items": {
|
|
820
|
+
"anyOf": [
|
|
821
|
+
{
|
|
822
|
+
"type": "string",
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
"type": "number",
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
"enum": [
|
|
829
|
+
null,
|
|
830
|
+
],
|
|
831
|
+
"nullable": true,
|
|
832
|
+
"type": "string",
|
|
833
|
+
},
|
|
834
|
+
],
|
|
835
|
+
},
|
|
836
|
+
"maxItems": 3,
|
|
837
|
+
"minItems": 3,
|
|
838
|
+
"type": "array",
|
|
839
|
+
}
|
|
840
|
+
`);
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
test("tuple draft-7", () => {
|
|
695
844
|
const schema = z.tuple([z.string(), z.number()]);
|
|
696
|
-
expect(z.toJSONSchema(schema, { target: "
|
|
845
|
+
expect(z.toJSONSchema(schema, { target: "draft-7", io: "input" })).toMatchInlineSnapshot(`
|
|
697
846
|
{
|
|
847
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
698
848
|
"items": [
|
|
699
849
|
{
|
|
700
850
|
"type": "string",
|
|
@@ -703,17 +853,19 @@ describe("toJSONSchema", () => {
|
|
|
703
853
|
"type": "number",
|
|
704
854
|
},
|
|
705
855
|
],
|
|
706
|
-
"maxItems": 2,
|
|
707
|
-
"minItems": 2,
|
|
708
856
|
"type": "array",
|
|
709
857
|
}
|
|
710
858
|
`);
|
|
711
859
|
});
|
|
712
860
|
|
|
713
|
-
test("tuple with rest
|
|
861
|
+
test("tuple with rest draft-7", () => {
|
|
714
862
|
const schema = z.tuple([z.string(), z.number()]).rest(z.boolean());
|
|
715
|
-
expect(z.toJSONSchema(schema, { target: "
|
|
863
|
+
expect(z.toJSONSchema(schema, { target: "draft-7", io: "input" })).toMatchInlineSnapshot(`
|
|
716
864
|
{
|
|
865
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
866
|
+
"additionalItems": {
|
|
867
|
+
"type": "boolean",
|
|
868
|
+
},
|
|
717
869
|
"items": [
|
|
718
870
|
{
|
|
719
871
|
"type": "string",
|
|
@@ -721,14 +873,60 @@ describe("toJSONSchema", () => {
|
|
|
721
873
|
{
|
|
722
874
|
"type": "number",
|
|
723
875
|
},
|
|
876
|
+
],
|
|
877
|
+
"type": "array",
|
|
878
|
+
}
|
|
879
|
+
`);
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
test("tuple with rest draft-7 - issue #5151 regression test", () => {
|
|
883
|
+
// This test addresses issue #5151: tuple with rest elements and ids
|
|
884
|
+
// in draft-7 had incorrect internal path handling affecting complex scenarios
|
|
885
|
+
const primarySchema = z.string().meta({ id: "primary" });
|
|
886
|
+
const restSchema = z.number().meta({ id: "rest" });
|
|
887
|
+
const testSchema = z.tuple([primarySchema], restSchema);
|
|
888
|
+
|
|
889
|
+
// Test both final output structure AND internal path handling
|
|
890
|
+
const capturedPaths: string[] = [];
|
|
891
|
+
const result = z.toJSONSchema(testSchema, {
|
|
892
|
+
target: "draft-7",
|
|
893
|
+
override: (ctx) => capturedPaths.push(ctx.path.join("/")),
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
// Verify correct draft-7 structure with metadata extraction
|
|
897
|
+
expect(result).toMatchInlineSnapshot(`
|
|
898
|
+
{
|
|
899
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
900
|
+
"additionalItems": {
|
|
901
|
+
"$ref": "#/definitions/rest",
|
|
902
|
+
},
|
|
903
|
+
"definitions": {
|
|
904
|
+
"primary": {
|
|
905
|
+
"id": "primary",
|
|
906
|
+
"type": "string",
|
|
907
|
+
},
|
|
908
|
+
"rest": {
|
|
909
|
+
"id": "rest",
|
|
910
|
+
"type": "number",
|
|
911
|
+
},
|
|
912
|
+
},
|
|
913
|
+
"items": [
|
|
724
914
|
{
|
|
725
|
-
"
|
|
915
|
+
"$ref": "#/definitions/primary",
|
|
726
916
|
},
|
|
727
917
|
],
|
|
728
|
-
"minItems": 2,
|
|
729
918
|
"type": "array",
|
|
730
919
|
}
|
|
731
920
|
`);
|
|
921
|
+
|
|
922
|
+
// Verify internal paths are correct (this was the actual bug)
|
|
923
|
+
expect(capturedPaths).toContain("items/0"); // prefix items should use "items" path
|
|
924
|
+
expect(capturedPaths).toContain("additionalItems"); // rest should use "additionalItems" path
|
|
925
|
+
expect(capturedPaths).not.toContain("prefixItems/0"); // should not use draft-2020-12 paths
|
|
926
|
+
|
|
927
|
+
// Structural validations
|
|
928
|
+
expect(Array.isArray(result.items)).toBe(true);
|
|
929
|
+
expect(result.additionalItems).toBeDefined();
|
|
732
930
|
});
|
|
733
931
|
|
|
734
932
|
test("promise", () => {
|
|
@@ -1554,7 +1752,9 @@ test("unrepresentable literal values are ignored", () => {
|
|
|
1554
1752
|
}
|
|
1555
1753
|
`);
|
|
1556
1754
|
|
|
1557
|
-
const b = z.z.toJSONSchema(z.literal([undefined, null, 5, BigInt(1324)]), {
|
|
1755
|
+
const b = z.z.toJSONSchema(z.literal([undefined, null, 5, BigInt(1324)]), {
|
|
1756
|
+
unrepresentable: "any",
|
|
1757
|
+
});
|
|
1558
1758
|
expect(b).toMatchInlineSnapshot(`
|
|
1559
1759
|
{
|
|
1560
1760
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -1566,7 +1766,9 @@ test("unrepresentable literal values are ignored", () => {
|
|
|
1566
1766
|
}
|
|
1567
1767
|
`);
|
|
1568
1768
|
|
|
1569
|
-
const c = z.z.toJSONSchema(z.literal([undefined]), {
|
|
1769
|
+
const c = z.z.toJSONSchema(z.literal([undefined]), {
|
|
1770
|
+
unrepresentable: "any",
|
|
1771
|
+
});
|
|
1570
1772
|
expect(c).toMatchInlineSnapshot(`
|
|
1571
1773
|
{
|
|
1572
1774
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -1849,7 +2051,9 @@ test("basic registry", () => {
|
|
|
1849
2051
|
myRegistry.add(User, { id: "User" });
|
|
1850
2052
|
myRegistry.add(Post, { id: "Post" });
|
|
1851
2053
|
|
|
1852
|
-
const result = z.z.toJSONSchema(myRegistry, {
|
|
2054
|
+
const result = z.z.toJSONSchema(myRegistry, {
|
|
2055
|
+
uri: (id) => `https://example.com/${id}.json`,
|
|
2056
|
+
});
|
|
1853
2057
|
expect(result).toMatchInlineSnapshot(`
|
|
1854
2058
|
{
|
|
1855
2059
|
"schemas": {
|
|
@@ -183,7 +183,7 @@ export class JSONSchemaGenerator {
|
|
|
183
183
|
else json.type = "number";
|
|
184
184
|
|
|
185
185
|
if (typeof exclusiveMinimum === "number") {
|
|
186
|
-
if (this.target === "draft-4") {
|
|
186
|
+
if (this.target === "draft-4" || this.target === "openapi-3.0") {
|
|
187
187
|
json.minimum = exclusiveMinimum;
|
|
188
188
|
json.exclusiveMinimum = true;
|
|
189
189
|
} else {
|
|
@@ -199,7 +199,7 @@ export class JSONSchemaGenerator {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
if (typeof exclusiveMaximum === "number") {
|
|
202
|
-
if (this.target === "draft-4") {
|
|
202
|
+
if (this.target === "draft-4" || this.target === "openapi-3.0") {
|
|
203
203
|
json.maximum = exclusiveMaximum;
|
|
204
204
|
json.exclusiveMaximum = true;
|
|
205
205
|
} else {
|
|
@@ -236,7 +236,11 @@ export class JSONSchemaGenerator {
|
|
|
236
236
|
break;
|
|
237
237
|
}
|
|
238
238
|
case "null": {
|
|
239
|
-
|
|
239
|
+
if (this.target === "openapi-3.0") {
|
|
240
|
+
_json.type = "string";
|
|
241
|
+
_json.nullable = true;
|
|
242
|
+
_json.enum = [null];
|
|
243
|
+
} else _json.type = "null";
|
|
240
244
|
break;
|
|
241
245
|
}
|
|
242
246
|
case "any": {
|
|
@@ -332,18 +336,7 @@ export class JSONSchemaGenerator {
|
|
|
332
336
|
path: [...params.path, "anyOf", i],
|
|
333
337
|
})
|
|
334
338
|
);
|
|
335
|
-
|
|
336
|
-
const nonNull = options.filter((x) => (x as any).type !== "null");
|
|
337
|
-
const hasNull = nonNull.length !== options.length;
|
|
338
|
-
if (nonNull.length === 1) {
|
|
339
|
-
Object.assign(json, nonNull[0]!);
|
|
340
|
-
} else {
|
|
341
|
-
json.anyOf = nonNull;
|
|
342
|
-
}
|
|
343
|
-
if (hasNull) (json as any).nullable = true;
|
|
344
|
-
} else {
|
|
345
|
-
json.anyOf = options;
|
|
346
|
-
}
|
|
339
|
+
json.anyOf = options;
|
|
347
340
|
break;
|
|
348
341
|
}
|
|
349
342
|
case "intersection": {
|
|
@@ -368,13 +361,21 @@ export class JSONSchemaGenerator {
|
|
|
368
361
|
case "tuple": {
|
|
369
362
|
const json: JSONSchema.ArraySchema = _json as any;
|
|
370
363
|
json.type = "array";
|
|
364
|
+
|
|
365
|
+
const prefixPath = this.target === "draft-2020-12" ? "prefixItems" : "items";
|
|
366
|
+
const restPath =
|
|
367
|
+
this.target === "draft-2020-12" ? "items" : this.target === "openapi-3.0" ? "items" : "additionalItems";
|
|
368
|
+
|
|
371
369
|
const prefixItems = def.items.map((x, i) =>
|
|
372
|
-
this.process(x, {
|
|
370
|
+
this.process(x, {
|
|
371
|
+
...params,
|
|
372
|
+
path: [...params.path, prefixPath, i],
|
|
373
|
+
})
|
|
373
374
|
);
|
|
374
375
|
const rest = def.rest
|
|
375
376
|
? this.process(def.rest, {
|
|
376
377
|
...params,
|
|
377
|
-
path: [...params.path, "items
|
|
378
|
+
path: [...params.path, restPath, ...(this.target === "openapi-3.0" ? [def.items.length] : [])],
|
|
378
379
|
})
|
|
379
380
|
: null;
|
|
380
381
|
|
|
@@ -384,9 +385,12 @@ export class JSONSchemaGenerator {
|
|
|
384
385
|
json.items = rest;
|
|
385
386
|
}
|
|
386
387
|
} else if (this.target === "openapi-3.0") {
|
|
387
|
-
json.items =
|
|
388
|
+
json.items = {
|
|
389
|
+
anyOf: prefixItems,
|
|
390
|
+
};
|
|
391
|
+
|
|
388
392
|
if (rest) {
|
|
389
|
-
json.items.push(rest);
|
|
393
|
+
json.items.anyOf!.push(rest);
|
|
390
394
|
}
|
|
391
395
|
json.minItems = prefixItems.length;
|
|
392
396
|
if (!rest) {
|
|
@@ -411,7 +415,7 @@ export class JSONSchemaGenerator {
|
|
|
411
415
|
case "record": {
|
|
412
416
|
const json: JSONSchema.ObjectSchema = _json as any;
|
|
413
417
|
json.type = "object";
|
|
414
|
-
if (this.target
|
|
418
|
+
if (this.target === "draft-7" || this.target === "draft-2020-12") {
|
|
415
419
|
json.propertyNames = this.process(def.keyType, {
|
|
416
420
|
...params,
|
|
417
421
|
path: [...params.path, "propertyNames"],
|
|
@@ -524,9 +528,8 @@ export class JSONSchemaGenerator {
|
|
|
524
528
|
case "nullable": {
|
|
525
529
|
const inner = this.process(def.innerType, params);
|
|
526
530
|
if (this.target === "openapi-3.0") {
|
|
527
|
-
Object.assign(_json, inner);
|
|
528
|
-
(_json as any).nullable = true;
|
|
529
531
|
result.ref = def.innerType;
|
|
532
|
+
_json.nullable = true;
|
|
530
533
|
} else {
|
|
531
534
|
_json.anyOf = [inner, { type: "null" }];
|
|
532
535
|
}
|
package/src/v4/core/versions.ts
CHANGED
|
@@ -101,7 +101,7 @@ class JSONSchemaGenerator {
|
|
|
101
101
|
else
|
|
102
102
|
json.type = "number";
|
|
103
103
|
if (typeof exclusiveMinimum === "number") {
|
|
104
|
-
if (this.target === "draft-4") {
|
|
104
|
+
if (this.target === "draft-4" || this.target === "openapi-3.0") {
|
|
105
105
|
json.minimum = exclusiveMinimum;
|
|
106
106
|
json.exclusiveMinimum = true;
|
|
107
107
|
}
|
|
@@ -119,7 +119,7 @@ class JSONSchemaGenerator {
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
if (typeof exclusiveMaximum === "number") {
|
|
122
|
-
if (this.target === "draft-4") {
|
|
122
|
+
if (this.target === "draft-4" || this.target === "openapi-3.0") {
|
|
123
123
|
json.maximum = exclusiveMaximum;
|
|
124
124
|
json.exclusiveMaximum = true;
|
|
125
125
|
}
|
|
@@ -158,7 +158,13 @@ class JSONSchemaGenerator {
|
|
|
158
158
|
break;
|
|
159
159
|
}
|
|
160
160
|
case "null": {
|
|
161
|
-
|
|
161
|
+
if (this.target === "openapi-3.0") {
|
|
162
|
+
_json.type = "string";
|
|
163
|
+
_json.nullable = true;
|
|
164
|
+
_json.enum = [null];
|
|
165
|
+
}
|
|
166
|
+
else
|
|
167
|
+
_json.type = "null";
|
|
162
168
|
break;
|
|
163
169
|
}
|
|
164
170
|
case "any": {
|
|
@@ -250,21 +256,7 @@ class JSONSchemaGenerator {
|
|
|
250
256
|
...params,
|
|
251
257
|
path: [...params.path, "anyOf", i],
|
|
252
258
|
}));
|
|
253
|
-
|
|
254
|
-
const nonNull = options.filter((x) => x.type !== "null");
|
|
255
|
-
const hasNull = nonNull.length !== options.length;
|
|
256
|
-
if (nonNull.length === 1) {
|
|
257
|
-
Object.assign(json, nonNull[0]);
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
260
|
-
json.anyOf = nonNull;
|
|
261
|
-
}
|
|
262
|
-
if (hasNull)
|
|
263
|
-
json.nullable = true;
|
|
264
|
-
}
|
|
265
|
-
else {
|
|
266
|
-
json.anyOf = options;
|
|
267
|
-
}
|
|
259
|
+
json.anyOf = options;
|
|
268
260
|
break;
|
|
269
261
|
}
|
|
270
262
|
case "intersection": {
|
|
@@ -288,11 +280,16 @@ class JSONSchemaGenerator {
|
|
|
288
280
|
case "tuple": {
|
|
289
281
|
const json = _json;
|
|
290
282
|
json.type = "array";
|
|
291
|
-
const
|
|
283
|
+
const prefixPath = this.target === "draft-2020-12" ? "prefixItems" : "items";
|
|
284
|
+
const restPath = this.target === "draft-2020-12" ? "items" : this.target === "openapi-3.0" ? "items" : "additionalItems";
|
|
285
|
+
const prefixItems = def.items.map((x, i) => this.process(x, {
|
|
286
|
+
...params,
|
|
287
|
+
path: [...params.path, prefixPath, i],
|
|
288
|
+
}));
|
|
292
289
|
const rest = def.rest
|
|
293
290
|
? this.process(def.rest, {
|
|
294
291
|
...params,
|
|
295
|
-
path: [...params.path, "items
|
|
292
|
+
path: [...params.path, restPath, ...(this.target === "openapi-3.0" ? [def.items.length] : [])],
|
|
296
293
|
})
|
|
297
294
|
: null;
|
|
298
295
|
if (this.target === "draft-2020-12") {
|
|
@@ -302,9 +299,11 @@ class JSONSchemaGenerator {
|
|
|
302
299
|
}
|
|
303
300
|
}
|
|
304
301
|
else if (this.target === "openapi-3.0") {
|
|
305
|
-
json.items =
|
|
302
|
+
json.items = {
|
|
303
|
+
anyOf: prefixItems,
|
|
304
|
+
};
|
|
306
305
|
if (rest) {
|
|
307
|
-
json.items.push(rest);
|
|
306
|
+
json.items.anyOf.push(rest);
|
|
308
307
|
}
|
|
309
308
|
json.minItems = prefixItems.length;
|
|
310
309
|
if (!rest) {
|
|
@@ -328,7 +327,7 @@ class JSONSchemaGenerator {
|
|
|
328
327
|
case "record": {
|
|
329
328
|
const json = _json;
|
|
330
329
|
json.type = "object";
|
|
331
|
-
if (this.target
|
|
330
|
+
if (this.target === "draft-7" || this.target === "draft-2020-12") {
|
|
332
331
|
json.propertyNames = this.process(def.keyType, {
|
|
333
332
|
...params,
|
|
334
333
|
path: [...params.path, "propertyNames"],
|
|
@@ -454,9 +453,8 @@ class JSONSchemaGenerator {
|
|
|
454
453
|
case "nullable": {
|
|
455
454
|
const inner = this.process(def.innerType, params);
|
|
456
455
|
if (this.target === "openapi-3.0") {
|
|
457
|
-
Object.assign(_json, inner);
|
|
458
|
-
_json.nullable = true;
|
|
459
456
|
result.ref = def.innerType;
|
|
457
|
+
_json.nullable = true;
|
|
460
458
|
}
|
|
461
459
|
else {
|
|
462
460
|
_json.anyOf = [inner, { type: "null" }];
|
|
@@ -97,7 +97,7 @@ export class JSONSchemaGenerator {
|
|
|
97
97
|
else
|
|
98
98
|
json.type = "number";
|
|
99
99
|
if (typeof exclusiveMinimum === "number") {
|
|
100
|
-
if (this.target === "draft-4") {
|
|
100
|
+
if (this.target === "draft-4" || this.target === "openapi-3.0") {
|
|
101
101
|
json.minimum = exclusiveMinimum;
|
|
102
102
|
json.exclusiveMinimum = true;
|
|
103
103
|
}
|
|
@@ -115,7 +115,7 @@ export class JSONSchemaGenerator {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
if (typeof exclusiveMaximum === "number") {
|
|
118
|
-
if (this.target === "draft-4") {
|
|
118
|
+
if (this.target === "draft-4" || this.target === "openapi-3.0") {
|
|
119
119
|
json.maximum = exclusiveMaximum;
|
|
120
120
|
json.exclusiveMaximum = true;
|
|
121
121
|
}
|
|
@@ -154,7 +154,13 @@ export class JSONSchemaGenerator {
|
|
|
154
154
|
break;
|
|
155
155
|
}
|
|
156
156
|
case "null": {
|
|
157
|
-
|
|
157
|
+
if (this.target === "openapi-3.0") {
|
|
158
|
+
_json.type = "string";
|
|
159
|
+
_json.nullable = true;
|
|
160
|
+
_json.enum = [null];
|
|
161
|
+
}
|
|
162
|
+
else
|
|
163
|
+
_json.type = "null";
|
|
158
164
|
break;
|
|
159
165
|
}
|
|
160
166
|
case "any": {
|
|
@@ -246,21 +252,7 @@ export class JSONSchemaGenerator {
|
|
|
246
252
|
...params,
|
|
247
253
|
path: [...params.path, "anyOf", i],
|
|
248
254
|
}));
|
|
249
|
-
|
|
250
|
-
const nonNull = options.filter((x) => x.type !== "null");
|
|
251
|
-
const hasNull = nonNull.length !== options.length;
|
|
252
|
-
if (nonNull.length === 1) {
|
|
253
|
-
Object.assign(json, nonNull[0]);
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
json.anyOf = nonNull;
|
|
257
|
-
}
|
|
258
|
-
if (hasNull)
|
|
259
|
-
json.nullable = true;
|
|
260
|
-
}
|
|
261
|
-
else {
|
|
262
|
-
json.anyOf = options;
|
|
263
|
-
}
|
|
255
|
+
json.anyOf = options;
|
|
264
256
|
break;
|
|
265
257
|
}
|
|
266
258
|
case "intersection": {
|
|
@@ -284,11 +276,16 @@ export class JSONSchemaGenerator {
|
|
|
284
276
|
case "tuple": {
|
|
285
277
|
const json = _json;
|
|
286
278
|
json.type = "array";
|
|
287
|
-
const
|
|
279
|
+
const prefixPath = this.target === "draft-2020-12" ? "prefixItems" : "items";
|
|
280
|
+
const restPath = this.target === "draft-2020-12" ? "items" : this.target === "openapi-3.0" ? "items" : "additionalItems";
|
|
281
|
+
const prefixItems = def.items.map((x, i) => this.process(x, {
|
|
282
|
+
...params,
|
|
283
|
+
path: [...params.path, prefixPath, i],
|
|
284
|
+
}));
|
|
288
285
|
const rest = def.rest
|
|
289
286
|
? this.process(def.rest, {
|
|
290
287
|
...params,
|
|
291
|
-
path: [...params.path, "items
|
|
288
|
+
path: [...params.path, restPath, ...(this.target === "openapi-3.0" ? [def.items.length] : [])],
|
|
292
289
|
})
|
|
293
290
|
: null;
|
|
294
291
|
if (this.target === "draft-2020-12") {
|
|
@@ -298,9 +295,11 @@ export class JSONSchemaGenerator {
|
|
|
298
295
|
}
|
|
299
296
|
}
|
|
300
297
|
else if (this.target === "openapi-3.0") {
|
|
301
|
-
json.items =
|
|
298
|
+
json.items = {
|
|
299
|
+
anyOf: prefixItems,
|
|
300
|
+
};
|
|
302
301
|
if (rest) {
|
|
303
|
-
json.items.push(rest);
|
|
302
|
+
json.items.anyOf.push(rest);
|
|
304
303
|
}
|
|
305
304
|
json.minItems = prefixItems.length;
|
|
306
305
|
if (!rest) {
|
|
@@ -324,7 +323,7 @@ export class JSONSchemaGenerator {
|
|
|
324
323
|
case "record": {
|
|
325
324
|
const json = _json;
|
|
326
325
|
json.type = "object";
|
|
327
|
-
if (this.target
|
|
326
|
+
if (this.target === "draft-7" || this.target === "draft-2020-12") {
|
|
328
327
|
json.propertyNames = this.process(def.keyType, {
|
|
329
328
|
...params,
|
|
330
329
|
path: [...params.path, "propertyNames"],
|
|
@@ -450,9 +449,8 @@ export class JSONSchemaGenerator {
|
|
|
450
449
|
case "nullable": {
|
|
451
450
|
const inner = this.process(def.innerType, params);
|
|
452
451
|
if (this.target === "openapi-3.0") {
|
|
453
|
-
Object.assign(_json, inner);
|
|
454
|
-
_json.nullable = true;
|
|
455
452
|
result.ref = def.innerType;
|
|
453
|
+
_json.nullable = true;
|
|
456
454
|
}
|
|
457
455
|
else {
|
|
458
456
|
_json.anyOf = [inner, { type: "null" }];
|
package/v4/core/versions.cjs
CHANGED
package/v4/core/versions.js
CHANGED