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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zod",
3
- "version": "4.1.2",
3
+ "version": "4.1.4",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Colin McDonnell <zod@colinhacks.com>",
@@ -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
- expect(z.toJSONSchema(z.string().nullable(), { target: "openapi-3.0" })).toMatchInlineSnapshot(`
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
- expect(z.toJSONSchema(schema, { target: "openapi-3.0" })).toMatchInlineSnapshot(`
596
+ const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
597
+ validateOpenAPI30Schema(jsonSchema);
598
+ expect(jsonSchema).toMatchInlineSnapshot(`
567
599
  {
568
- "nullable": true,
569
- "type": "string",
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: "openapi-3.0" })).toMatchInlineSnapshot(`
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 openapi", () => {
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: "openapi-3.0" })).toMatchInlineSnapshot(`
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
- "type": "boolean",
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)]), { unrepresentable: "any" });
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]), { unrepresentable: "any" });
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, { uri: (id) => `https://example.com/${id}.json` });
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
- _json.type = "null";
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
- if (this.target === "openapi-3.0") {
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, { ...params, path: [...params.path, "prefixItems", i] })
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 = [...prefixItems];
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 !== "draft-4") {
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
  }
@@ -1,5 +1,5 @@
1
1
  export const version = {
2
2
  major: 4,
3
3
  minor: 1,
4
- patch: 2 as number,
4
+ patch: 4 as number,
5
5
  } as const;
@@ -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
- _json.type = "null";
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
- if (this.target === "openapi-3.0") {
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 prefixItems = def.items.map((x, i) => this.process(x, { ...params, path: [...params.path, "prefixItems", i] }));
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 = [...prefixItems];
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 !== "draft-4") {
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
- _json.type = "null";
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
- if (this.target === "openapi-3.0") {
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 prefixItems = def.items.map((x, i) => this.process(x, { ...params, path: [...params.path, "prefixItems", i] }));
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 = [...prefixItems];
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 !== "draft-4") {
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" }];
@@ -4,5 +4,5 @@ exports.version = void 0;
4
4
  exports.version = {
5
5
  major: 4,
6
6
  minor: 1,
7
- patch: 2,
7
+ patch: 4,
8
8
  };
@@ -1,5 +1,5 @@
1
1
  export const version = {
2
2
  major: 4,
3
3
  minor: 1,
4
- patch: 2,
4
+ patch: 4,
5
5
  };