zod 4.1.3 → 4.1.5
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/schemas.ts +2 -2
- package/src/v4/classic/tests/function.test.ts +40 -0
- package/src/v4/classic/tests/to-json-schema.test.ts +95 -15
- package/src/v4/core/to-json-schema.ts +9 -16
- package/src/v4/core/versions.ts +1 -1
- package/v4/classic/schemas.d.cts +2 -2
- package/v4/classic/schemas.d.ts +2 -2
- package/v4/core/to-json-schema.cjs +10 -19
- package/v4/core/to-json-schema.js +10 -19
- package/v4/core/versions.cjs +1 -1
- package/v4/core/versions.js +1 -1
package/package.json
CHANGED
|
@@ -2059,11 +2059,11 @@ export const ZodFunction: core.$constructor<ZodFunction> = /*@__PURE__*/ core.$c
|
|
|
2059
2059
|
);
|
|
2060
2060
|
|
|
2061
2061
|
export function _function(): ZodFunction;
|
|
2062
|
-
export function _function<const In extends
|
|
2062
|
+
export function _function<const In extends ReadonlyArray<core.$ZodType>>(params: {
|
|
2063
2063
|
input: In;
|
|
2064
2064
|
}): ZodFunction<ZodTuple<In, null>, core.$ZodFunctionOut>;
|
|
2065
2065
|
export function _function<
|
|
2066
|
-
const In extends
|
|
2066
|
+
const In extends ReadonlyArray<core.$ZodType>,
|
|
2067
2067
|
const Out extends core.$ZodFunctionOut = core.$ZodFunctionOut,
|
|
2068
2068
|
>(params: {
|
|
2069
2069
|
input: In;
|
|
@@ -130,6 +130,46 @@ test("valid function run", () => {
|
|
|
130
130
|
});
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
const args3 = [
|
|
134
|
+
z.object({
|
|
135
|
+
f1: z.number(),
|
|
136
|
+
f2: z.string().nullable(),
|
|
137
|
+
f3: z.array(z.boolean().optional()).optional(),
|
|
138
|
+
}),
|
|
139
|
+
] as const;
|
|
140
|
+
const returns3 = z.union([z.string(), z.number()]);
|
|
141
|
+
|
|
142
|
+
const func3 = z.function({
|
|
143
|
+
input: args3,
|
|
144
|
+
output: returns3,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("function inference 3", () => {
|
|
148
|
+
type func3 = (typeof func3)["_input"];
|
|
149
|
+
|
|
150
|
+
expectTypeOf<func3>().toEqualTypeOf<
|
|
151
|
+
(arg: {
|
|
152
|
+
f3?: (boolean | undefined)[] | undefined;
|
|
153
|
+
f1: number;
|
|
154
|
+
f2: string | null;
|
|
155
|
+
}) => string | number
|
|
156
|
+
>();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("valid function run", () => {
|
|
160
|
+
const validFunc3Instance = func3.implement((_x) => {
|
|
161
|
+
_x.f2;
|
|
162
|
+
_x.f3![0];
|
|
163
|
+
return "adf" as any;
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
validFunc3Instance({
|
|
167
|
+
f1: 21,
|
|
168
|
+
f2: "asdf",
|
|
169
|
+
f3: [true, false],
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
133
173
|
test("input validation error", () => {
|
|
134
174
|
const schema = z.function({
|
|
135
175
|
input: z.tuple([z.string()]),
|
|
@@ -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,19 +591,33 @@ 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
|
+
],
|
|
570
612
|
}
|
|
571
613
|
`);
|
|
572
614
|
});
|
|
573
615
|
|
|
574
|
-
test("number with exclusive min-max openapi", () => {
|
|
616
|
+
test("number with exclusive min-max openapi-3.0", () => {
|
|
575
617
|
const schema = z.number().lt(100).gt(1);
|
|
576
|
-
|
|
618
|
+
const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
|
|
619
|
+
validateOpenAPI30Schema(jsonSchema);
|
|
620
|
+
expect(jsonSchema).toMatchInlineSnapshot(`
|
|
577
621
|
{
|
|
578
622
|
"exclusiveMaximum": true,
|
|
579
623
|
"exclusiveMinimum": true,
|
|
@@ -665,9 +709,11 @@ describe("toJSONSchema", () => {
|
|
|
665
709
|
`);
|
|
666
710
|
});
|
|
667
711
|
|
|
668
|
-
test("record openapi", () => {
|
|
712
|
+
test("record openapi-3.0", () => {
|
|
669
713
|
const schema = z.record(z.string(), z.boolean());
|
|
670
|
-
|
|
714
|
+
const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
|
|
715
|
+
validateOpenAPI30Schema(jsonSchema);
|
|
716
|
+
expect(jsonSchema).toMatchInlineSnapshot(`
|
|
671
717
|
{
|
|
672
718
|
"additionalProperties": {
|
|
673
719
|
"type": "boolean",
|
|
@@ -716,9 +762,11 @@ describe("toJSONSchema", () => {
|
|
|
716
762
|
`);
|
|
717
763
|
});
|
|
718
764
|
|
|
719
|
-
test("tuple openapi", () => {
|
|
765
|
+
test("tuple openapi-3.0", () => {
|
|
720
766
|
const schema = z.tuple([z.string(), z.number()]);
|
|
721
|
-
|
|
767
|
+
const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
|
|
768
|
+
validateOpenAPI30Schema(jsonSchema);
|
|
769
|
+
expect(jsonSchema).toMatchInlineSnapshot(`
|
|
722
770
|
{
|
|
723
771
|
"items": {
|
|
724
772
|
"anyOf": [
|
|
@@ -737,9 +785,11 @@ describe("toJSONSchema", () => {
|
|
|
737
785
|
`);
|
|
738
786
|
});
|
|
739
787
|
|
|
740
|
-
test("tuple with rest openapi", () => {
|
|
788
|
+
test("tuple with rest openapi-3.0", () => {
|
|
741
789
|
const schema = z.tuple([z.string(), z.number()]).rest(z.boolean());
|
|
742
|
-
|
|
790
|
+
const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
|
|
791
|
+
validateOpenAPI30Schema(jsonSchema);
|
|
792
|
+
expect(jsonSchema).toMatchInlineSnapshot(`
|
|
743
793
|
{
|
|
744
794
|
"items": {
|
|
745
795
|
"anyOf": [
|
|
@@ -754,7 +804,37 @@ describe("toJSONSchema", () => {
|
|
|
754
804
|
},
|
|
755
805
|
],
|
|
756
806
|
},
|
|
757
|
-
"minItems":
|
|
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,
|
|
758
838
|
"type": "array",
|
|
759
839
|
}
|
|
760
840
|
`);
|
|
@@ -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": {
|
|
@@ -393,8 +386,9 @@ export class JSONSchemaGenerator {
|
|
|
393
386
|
}
|
|
394
387
|
} else if (this.target === "openapi-3.0") {
|
|
395
388
|
json.items = {
|
|
396
|
-
anyOf:
|
|
389
|
+
anyOf: prefixItems,
|
|
397
390
|
};
|
|
391
|
+
|
|
398
392
|
if (rest) {
|
|
399
393
|
json.items.anyOf!.push(rest);
|
|
400
394
|
}
|
|
@@ -534,9 +528,8 @@ export class JSONSchemaGenerator {
|
|
|
534
528
|
case "nullable": {
|
|
535
529
|
const inner = this.process(def.innerType, params);
|
|
536
530
|
if (this.target === "openapi-3.0") {
|
|
537
|
-
Object.assign(_json, inner);
|
|
538
|
-
(_json as any).nullable = true;
|
|
539
531
|
result.ref = def.innerType;
|
|
532
|
+
_json.nullable = true;
|
|
540
533
|
} else {
|
|
541
534
|
_json.anyOf = [inner, { type: "null" }];
|
|
542
535
|
}
|
package/src/v4/core/versions.ts
CHANGED
package/v4/classic/schemas.d.cts
CHANGED
|
@@ -624,10 +624,10 @@ export interface ZodFunction<Args extends core.$ZodFunctionIn = core.$ZodFunctio
|
|
|
624
624
|
}
|
|
625
625
|
export declare const ZodFunction: core.$constructor<ZodFunction>;
|
|
626
626
|
export declare function _function(): ZodFunction;
|
|
627
|
-
export declare function _function<const In extends
|
|
627
|
+
export declare function _function<const In extends ReadonlyArray<core.$ZodType>>(params: {
|
|
628
628
|
input: In;
|
|
629
629
|
}): ZodFunction<ZodTuple<In, null>, core.$ZodFunctionOut>;
|
|
630
|
-
export declare function _function<const In extends
|
|
630
|
+
export declare function _function<const In extends ReadonlyArray<core.$ZodType>, const Out extends core.$ZodFunctionOut = core.$ZodFunctionOut>(params: {
|
|
631
631
|
input: In;
|
|
632
632
|
output: Out;
|
|
633
633
|
}): ZodFunction<ZodTuple<In, null>, Out>;
|
package/v4/classic/schemas.d.ts
CHANGED
|
@@ -624,10 +624,10 @@ export interface ZodFunction<Args extends core.$ZodFunctionIn = core.$ZodFunctio
|
|
|
624
624
|
}
|
|
625
625
|
export declare const ZodFunction: core.$constructor<ZodFunction>;
|
|
626
626
|
export declare function _function(): ZodFunction;
|
|
627
|
-
export declare function _function<const In extends
|
|
627
|
+
export declare function _function<const In extends ReadonlyArray<core.$ZodType>>(params: {
|
|
628
628
|
input: In;
|
|
629
629
|
}): ZodFunction<ZodTuple<In, null>, core.$ZodFunctionOut>;
|
|
630
|
-
export declare function _function<const In extends
|
|
630
|
+
export declare function _function<const In extends ReadonlyArray<core.$ZodType>, const Out extends core.$ZodFunctionOut = core.$ZodFunctionOut>(params: {
|
|
631
631
|
input: In;
|
|
632
632
|
output: Out;
|
|
633
633
|
}): ZodFunction<ZodTuple<In, null>, Out>;
|
|
@@ -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": {
|
|
@@ -308,7 +300,7 @@ class JSONSchemaGenerator {
|
|
|
308
300
|
}
|
|
309
301
|
else if (this.target === "openapi-3.0") {
|
|
310
302
|
json.items = {
|
|
311
|
-
anyOf:
|
|
303
|
+
anyOf: prefixItems,
|
|
312
304
|
};
|
|
313
305
|
if (rest) {
|
|
314
306
|
json.items.anyOf.push(rest);
|
|
@@ -461,9 +453,8 @@ class JSONSchemaGenerator {
|
|
|
461
453
|
case "nullable": {
|
|
462
454
|
const inner = this.process(def.innerType, params);
|
|
463
455
|
if (this.target === "openapi-3.0") {
|
|
464
|
-
Object.assign(_json, inner);
|
|
465
|
-
_json.nullable = true;
|
|
466
456
|
result.ref = def.innerType;
|
|
457
|
+
_json.nullable = true;
|
|
467
458
|
}
|
|
468
459
|
else {
|
|
469
460
|
_json.anyOf = [inner, { type: "null" }];
|
|
@@ -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": {
|
|
@@ -304,7 +296,7 @@ export class JSONSchemaGenerator {
|
|
|
304
296
|
}
|
|
305
297
|
else if (this.target === "openapi-3.0") {
|
|
306
298
|
json.items = {
|
|
307
|
-
anyOf:
|
|
299
|
+
anyOf: prefixItems,
|
|
308
300
|
};
|
|
309
301
|
if (rest) {
|
|
310
302
|
json.items.anyOf.push(rest);
|
|
@@ -457,9 +449,8 @@ export class JSONSchemaGenerator {
|
|
|
457
449
|
case "nullable": {
|
|
458
450
|
const inner = this.process(def.innerType, params);
|
|
459
451
|
if (this.target === "openapi-3.0") {
|
|
460
|
-
Object.assign(_json, inner);
|
|
461
|
-
_json.nullable = true;
|
|
462
452
|
result.ref = def.innerType;
|
|
453
|
+
_json.nullable = true;
|
|
463
454
|
}
|
|
464
455
|
else {
|
|
465
456
|
_json.anyOf = [inner, { type: "null" }];
|
package/v4/core/versions.cjs
CHANGED
package/v4/core/versions.js
CHANGED