zod 4.1.12 → 4.1.13
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/checks.ts +1 -0
- package/src/v4/classic/schemas.ts +20 -0
- package/src/v4/classic/tests/continuability.test.ts +22 -0
- package/src/v4/classic/tests/describe-meta-checks.test.ts +27 -0
- package/src/v4/classic/tests/index.test.ts +55 -1
- package/src/v4/classic/tests/promise.test.ts +1 -1
- package/src/v4/classic/tests/readonly.test.ts +1 -1
- package/src/v4/classic/tests/record.test.ts +141 -9
- package/src/v4/classic/tests/registries.test.ts +5 -1
- package/src/v4/classic/tests/string.test.ts +72 -0
- package/src/v4/classic/tests/template-literal.test.ts +8 -0
- package/src/v4/classic/tests/to-json-schema.test.ts +97 -0
- package/src/v4/classic/tests/tuple.test.ts +18 -0
- package/src/v4/classic/tests/url.test.ts +13 -0
- package/src/v4/core/api.ts +45 -0
- package/src/v4/core/core.ts +22 -9
- package/src/v4/core/regexes.ts +6 -1
- package/src/v4/core/registries.ts +12 -1
- package/src/v4/core/schemas.ts +50 -33
- package/src/v4/core/tests/extend.test.ts +42 -1
- package/src/v4/core/tests/locales/he.test.ts +379 -0
- package/src/v4/core/tests/locales/nl.test.ts +46 -0
- package/src/v4/core/tests/record-constructor.test.ts +67 -0
- package/src/v4/core/tests/recursive-tuples.test.ts +45 -0
- package/src/v4/core/to-json-schema.ts +55 -91
- package/src/v4/core/util.ts +11 -0
- package/src/v4/core/versions.ts +1 -1
- package/src/v4/locales/en.ts +1 -0
- package/src/v4/locales/he.ts +202 -71
- package/src/v4/locales/nl.ts +10 -10
- package/src/v4/mini/iso.ts +4 -4
- package/src/v4/mini/schemas.ts +17 -0
- package/src/v4/mini/tests/functions.test.ts +0 -38
- package/src/v4/mini/tests/index.test.ts +24 -1
- package/src/v4/mini/tests/string.test.ts +32 -0
- package/v3/ZodError.d.cts +1 -1
- package/v3/ZodError.d.ts +1 -1
- package/v4/classic/checks.cjs +2 -1
- package/v4/classic/checks.d.cts +1 -1
- package/v4/classic/checks.d.ts +1 -1
- package/v4/classic/checks.js +1 -1
- package/v4/classic/schemas.cjs +15 -2
- package/v4/classic/schemas.d.cts +8 -0
- package/v4/classic/schemas.d.ts +8 -0
- package/v4/classic/schemas.js +12 -0
- package/v4/core/api.cjs +40 -0
- package/v4/core/api.d.cts +7 -0
- package/v4/core/api.d.ts +7 -0
- package/v4/core/api.js +36 -0
- package/v4/core/core.cjs +20 -11
- package/v4/core/core.js +20 -11
- package/v4/core/regexes.cjs +31 -2
- package/v4/core/regexes.d.cts +1 -0
- package/v4/core/regexes.d.ts +1 -0
- package/v4/core/regexes.js +5 -0
- package/v4/core/registries.cjs +3 -1
- package/v4/core/registries.js +3 -1
- package/v4/core/schemas.cjs +32 -33
- package/v4/core/schemas.d.cts +12 -2
- package/v4/core/schemas.d.ts +12 -2
- package/v4/core/schemas.js +30 -31
- package/v4/core/to-json-schema.cjs +55 -92
- package/v4/core/to-json-schema.js +55 -92
- package/v4/core/util.cjs +11 -0
- package/v4/core/util.d.cts +1 -0
- package/v4/core/util.d.ts +1 -0
- package/v4/core/util.js +10 -0
- package/v4/core/versions.cjs +1 -1
- package/v4/core/versions.js +1 -1
- package/v4/locales/en.cjs +1 -0
- package/v4/locales/en.js +1 -0
- package/v4/locales/he.cjs +177 -66
- package/v4/locales/he.js +177 -66
- package/v4/locales/nl.cjs +8 -8
- package/v4/locales/nl.js +8 -8
- package/v4/mini/iso.cjs +4 -4
- package/v4/mini/iso.js +4 -4
- package/v4/mini/schemas.cjs +13 -2
- package/v4/mini/schemas.d.cts +6 -0
- package/v4/mini/schemas.d.ts +6 -0
- package/v4/mini/schemas.js +10 -0
package/package.json
CHANGED
package/src/v4/classic/checks.ts
CHANGED
|
@@ -252,6 +252,7 @@ export interface _ZodString<T extends core.$ZodStringInternals<unknown> = core.$
|
|
|
252
252
|
normalize(form?: "NFC" | "NFD" | "NFKC" | "NFKD" | (string & {})): this;
|
|
253
253
|
toLowerCase(): this;
|
|
254
254
|
toUpperCase(): this;
|
|
255
|
+
slugify(): this;
|
|
255
256
|
}
|
|
256
257
|
|
|
257
258
|
/** @internal */
|
|
@@ -281,6 +282,7 @@ export const _ZodString: core.$constructor<_ZodString> = /*@__PURE__*/ core.$con
|
|
|
281
282
|
inst.normalize = (...args) => inst.check(checks.normalize(...args));
|
|
282
283
|
inst.toLowerCase = () => inst.check(checks.toLowerCase());
|
|
283
284
|
inst.toUpperCase = () => inst.check(checks.toUpperCase());
|
|
285
|
+
inst.slugify = () => inst.check(checks.slugify());
|
|
284
286
|
});
|
|
285
287
|
|
|
286
288
|
export interface ZodString extends _ZodString<core.$ZodStringInternals<string>> {
|
|
@@ -614,6 +616,19 @@ export function ipv4(params?: string | core.$ZodIPv4Params): ZodIPv4 {
|
|
|
614
616
|
return core._ipv4(ZodIPv4, params);
|
|
615
617
|
}
|
|
616
618
|
|
|
619
|
+
// ZodMAC
|
|
620
|
+
export interface ZodMAC extends ZodStringFormat<"mac"> {
|
|
621
|
+
_zod: core.$ZodMACInternals;
|
|
622
|
+
}
|
|
623
|
+
export const ZodMAC: core.$constructor<ZodMAC> = /*@__PURE__*/ core.$constructor("ZodMAC", (inst, def) => {
|
|
624
|
+
// ZodStringFormat.init(inst, def);
|
|
625
|
+
core.$ZodMAC.init(inst, def);
|
|
626
|
+
ZodStringFormat.init(inst, def);
|
|
627
|
+
});
|
|
628
|
+
export function mac(params?: string | core.$ZodMACParams): ZodMAC {
|
|
629
|
+
return core._mac(ZodMAC, params);
|
|
630
|
+
}
|
|
631
|
+
|
|
617
632
|
// ZodIPv6
|
|
618
633
|
export interface ZodIPv6 extends ZodStringFormat<"ipv6"> {
|
|
619
634
|
_zod: core.$ZodIPv6Internals;
|
|
@@ -792,6 +807,7 @@ export interface ZodNumber extends _ZodNumber<core.$ZodNumberInternals<number>>
|
|
|
792
807
|
|
|
793
808
|
export const ZodNumber: core.$constructor<ZodNumber> = /*@__PURE__*/ core.$constructor("ZodNumber", (inst, def) => {
|
|
794
809
|
core.$ZodNumber.init(inst, def);
|
|
810
|
+
|
|
795
811
|
ZodType.init(inst, def);
|
|
796
812
|
|
|
797
813
|
inst.gt = (value, params) => inst.check(checks.gt(value, params));
|
|
@@ -2128,6 +2144,10 @@ export function superRefine<T>(
|
|
|
2128
2144
|
return core._superRefine(fn);
|
|
2129
2145
|
}
|
|
2130
2146
|
|
|
2147
|
+
// Re-export describe and meta from core
|
|
2148
|
+
export const describe = core.describe;
|
|
2149
|
+
export const meta = core.meta;
|
|
2150
|
+
|
|
2131
2151
|
type ZodInstanceOfParams = core.Params<
|
|
2132
2152
|
ZodCustom,
|
|
2133
2153
|
core.$ZodIssueCustom,
|
|
@@ -195,6 +195,28 @@ test("continuability", () => {
|
|
|
195
195
|
},
|
|
196
196
|
]
|
|
197
197
|
`);
|
|
198
|
+
expect(
|
|
199
|
+
z
|
|
200
|
+
.mac()
|
|
201
|
+
.refine(() => false)
|
|
202
|
+
.safeParse("invalid_value").error!.issues
|
|
203
|
+
).toMatchInlineSnapshot(`
|
|
204
|
+
[
|
|
205
|
+
{
|
|
206
|
+
"code": "invalid_format",
|
|
207
|
+
"format": "mac",
|
|
208
|
+
"message": "Invalid MAC address",
|
|
209
|
+
"origin": "string",
|
|
210
|
+
"path": [],
|
|
211
|
+
"pattern": "/^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$/",
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"code": "custom",
|
|
215
|
+
"message": "Invalid input",
|
|
216
|
+
"path": [],
|
|
217
|
+
},
|
|
218
|
+
]
|
|
219
|
+
`);
|
|
198
220
|
expect(
|
|
199
221
|
z
|
|
200
222
|
.emoji()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import * as z from "../index.js";
|
|
3
|
+
|
|
4
|
+
describe("z.describe() check", () => {
|
|
5
|
+
it("registers description in globalRegistry", () => {
|
|
6
|
+
const schema = z.string().check(z.describe("A string"));
|
|
7
|
+
expect(z.globalRegistry.get(schema)?.description).toBe("A string");
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe("z.meta() check", () => {
|
|
12
|
+
it("registers metadata in globalRegistry", () => {
|
|
13
|
+
const schema = z.number().check(z.meta({ title: "Age", description: "User's age" }));
|
|
14
|
+
const meta = z.globalRegistry.get(schema);
|
|
15
|
+
expect(meta?.title).toBe("Age");
|
|
16
|
+
expect(meta?.description).toBe("User's age");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("combined usage", () => {
|
|
21
|
+
it("works with multiple checks", () => {
|
|
22
|
+
const schema = z.string().check(z.describe("Email address"), z.meta({ title: "Email" }));
|
|
23
|
+
const meta = z.globalRegistry.get(schema);
|
|
24
|
+
expect(meta?.description).toBe("Email address");
|
|
25
|
+
expect(meta?.title).toBe("Email");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -302,6 +302,29 @@ test("z.record", () => {
|
|
|
302
302
|
const d = z.record(z.enum(["a", "b"]).or(z.never()), z.string());
|
|
303
303
|
type d = z.output<typeof d>;
|
|
304
304
|
expectTypeOf<d>().toEqualTypeOf<Record<"a" | "b", string>>();
|
|
305
|
+
|
|
306
|
+
// literal union keys
|
|
307
|
+
const e = z.record(z.union([z.literal("a"), z.literal(0)]), z.string());
|
|
308
|
+
type e = z.output<typeof e>;
|
|
309
|
+
expectTypeOf<e>().toEqualTypeOf<Record<"a" | 0, string>>();
|
|
310
|
+
expect(z.parse(e, { a: "hello", 0: "world" })).toEqual({
|
|
311
|
+
a: "hello",
|
|
312
|
+
0: "world",
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// TypeScript enum keys
|
|
316
|
+
enum Enum {
|
|
317
|
+
A = 0,
|
|
318
|
+
B = "hi",
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const f = z.record(z.enum(Enum), z.string());
|
|
322
|
+
type f = z.output<typeof f>;
|
|
323
|
+
expectTypeOf<f>().toEqualTypeOf<Record<Enum, string>>();
|
|
324
|
+
expect(z.parse(f, { [Enum.A]: "hello", [Enum.B]: "world" })).toEqual({
|
|
325
|
+
[Enum.A]: "hello",
|
|
326
|
+
[Enum.B]: "world",
|
|
327
|
+
});
|
|
305
328
|
});
|
|
306
329
|
|
|
307
330
|
test("z.map", () => {
|
|
@@ -749,7 +772,7 @@ test("z.json", () => {
|
|
|
749
772
|
test("z.promise", async () => {
|
|
750
773
|
const a = z.promise(z.string());
|
|
751
774
|
type a = z.output<typeof a>;
|
|
752
|
-
expectTypeOf<a>().toEqualTypeOf<string
|
|
775
|
+
expectTypeOf<a>().toEqualTypeOf<Promise<string>>();
|
|
753
776
|
|
|
754
777
|
expect(await z.safeParseAsync(a, Promise.resolve("hello"))).toMatchObject({
|
|
755
778
|
success: true,
|
|
@@ -786,6 +809,37 @@ test("isPlainObject", () => {
|
|
|
786
809
|
expect(z.core.util.isPlainObject("string")).toEqual(false);
|
|
787
810
|
expect(z.core.util.isPlainObject(123)).toEqual(false);
|
|
788
811
|
expect(z.core.util.isPlainObject(Symbol())).toEqual(false);
|
|
812
|
+
expect(z.core.util.isPlainObject({ constructor: "string" })).toEqual(true);
|
|
813
|
+
expect(z.core.util.isPlainObject({ constructor: 123 })).toEqual(true);
|
|
814
|
+
expect(z.core.util.isPlainObject({ constructor: null })).toEqual(true);
|
|
815
|
+
expect(z.core.util.isPlainObject({ constructor: undefined })).toEqual(true);
|
|
816
|
+
expect(z.core.util.isPlainObject({ constructor: true })).toEqual(true);
|
|
817
|
+
expect(z.core.util.isPlainObject({ constructor: {} })).toEqual(true);
|
|
818
|
+
expect(z.core.util.isPlainObject({ constructor: [] })).toEqual(true);
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
test("shallowClone with constructor field", () => {
|
|
822
|
+
const objWithConstructor = { constructor: "string", key: "value" };
|
|
823
|
+
const cloned = z.core.util.shallowClone(objWithConstructor);
|
|
824
|
+
|
|
825
|
+
expect(cloned).toEqual(objWithConstructor);
|
|
826
|
+
expect(cloned).not.toBe(objWithConstructor);
|
|
827
|
+
expect(cloned.constructor).toBe("string");
|
|
828
|
+
expect(cloned.key).toBe("value");
|
|
829
|
+
|
|
830
|
+
const testCases = [
|
|
831
|
+
{ constructor: 123, data: "test" },
|
|
832
|
+
{ constructor: null, data: "test" },
|
|
833
|
+
{ constructor: true, data: "test" },
|
|
834
|
+
{ constructor: {}, data: "test" },
|
|
835
|
+
{ constructor: [], data: "test" },
|
|
836
|
+
];
|
|
837
|
+
|
|
838
|
+
for (const testCase of testCases) {
|
|
839
|
+
const clonedCase = z.core.util.shallowClone(testCase);
|
|
840
|
+
expect(clonedCase).toEqual(testCase);
|
|
841
|
+
expect(clonedCase).not.toBe(testCase);
|
|
842
|
+
}
|
|
789
843
|
});
|
|
790
844
|
|
|
791
845
|
test("def typing", () => {
|
|
@@ -10,7 +10,7 @@ const promSchema = z.promise(
|
|
|
10
10
|
|
|
11
11
|
test("promise inference", () => {
|
|
12
12
|
type promSchemaType = z.infer<typeof promSchema>;
|
|
13
|
-
expectTypeOf<promSchemaType>().toEqualTypeOf<{ name: string; age: number }
|
|
13
|
+
expectTypeOf<promSchemaType>().toEqualTypeOf<Promise<{ name: string; age: number }>>();
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
test("promise parsing success", async () => {
|
|
@@ -47,7 +47,7 @@ test("flat inference", () => {
|
|
|
47
47
|
expectTypeOf<typeof readonlyNumberRecord._output>().toEqualTypeOf<Readonly<Record<string, number>>>();
|
|
48
48
|
expectTypeOf<typeof readonlyObject._output>().toEqualTypeOf<{ readonly a: string; readonly 1: number }>();
|
|
49
49
|
expectTypeOf<typeof readonlyEnum._output>().toEqualTypeOf<Readonly<testEnum>>();
|
|
50
|
-
expectTypeOf<typeof readonlyPromise._output>().toEqualTypeOf<string
|
|
50
|
+
expectTypeOf<typeof readonlyPromise._output>().toEqualTypeOf<Promise<string>>();
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
// test("deep inference", () => {
|
|
@@ -8,16 +8,28 @@ test("type inference", () => {
|
|
|
8
8
|
const recordWithEnumKeys = z.record(z.enum(["Tuna", "Salmon"]), z.string());
|
|
9
9
|
type recordWithEnumKeys = z.infer<typeof recordWithEnumKeys>;
|
|
10
10
|
|
|
11
|
-
const recordWithLiteralKey = z.record(z.literal(["Tuna", "Salmon"]), z.string());
|
|
11
|
+
const recordWithLiteralKey = z.record(z.literal(["Tuna", "Salmon", 21]), z.string());
|
|
12
12
|
type recordWithLiteralKey = z.infer<typeof recordWithLiteralKey>;
|
|
13
13
|
|
|
14
|
-
const recordWithLiteralUnionKeys = z.record(
|
|
14
|
+
const recordWithLiteralUnionKeys = z.record(
|
|
15
|
+
z.union([z.literal("Tuna"), z.literal("Salmon"), z.literal(21)]),
|
|
16
|
+
z.string()
|
|
17
|
+
);
|
|
15
18
|
type recordWithLiteralUnionKeys = z.infer<typeof recordWithLiteralUnionKeys>;
|
|
16
19
|
|
|
20
|
+
enum Enum {
|
|
21
|
+
Tuna = 0,
|
|
22
|
+
Salmon = "Shark",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const recordWithTypescriptEnum = z.record(z.enum(Enum), z.string());
|
|
26
|
+
type recordWithTypescriptEnum = z.infer<typeof recordWithTypescriptEnum>;
|
|
27
|
+
|
|
17
28
|
expectTypeOf<booleanRecord>().toEqualTypeOf<Record<string, boolean>>();
|
|
18
29
|
expectTypeOf<recordWithEnumKeys>().toEqualTypeOf<Record<"Tuna" | "Salmon", string>>();
|
|
19
|
-
expectTypeOf<recordWithLiteralKey>().toEqualTypeOf<Record<"Tuna" | "Salmon", string>>();
|
|
20
|
-
expectTypeOf<recordWithLiteralUnionKeys>().toEqualTypeOf<Record<"Tuna" | "Salmon", string>>();
|
|
30
|
+
expectTypeOf<recordWithLiteralKey>().toEqualTypeOf<Record<"Tuna" | "Salmon" | 21, string>>();
|
|
31
|
+
expectTypeOf<recordWithLiteralUnionKeys>().toEqualTypeOf<Record<"Tuna" | "Salmon" | 21, string>>();
|
|
32
|
+
expectTypeOf<recordWithTypescriptEnum>().toEqualTypeOf<Record<Enum, string>>();
|
|
21
33
|
});
|
|
22
34
|
|
|
23
35
|
test("enum exhaustiveness", () => {
|
|
@@ -64,14 +76,76 @@ test("enum exhaustiveness", () => {
|
|
|
64
76
|
`);
|
|
65
77
|
});
|
|
66
78
|
|
|
79
|
+
test("typescript enum exhaustiveness", () => {
|
|
80
|
+
enum BigFish {
|
|
81
|
+
Tuna = 0,
|
|
82
|
+
Salmon = "Shark",
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const schema = z.record(z.enum(BigFish), z.string());
|
|
86
|
+
const value = {
|
|
87
|
+
[BigFish.Tuna]: "asdf",
|
|
88
|
+
[BigFish.Salmon]: "asdf",
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
expect(schema.parse(value)).toEqual(value);
|
|
92
|
+
|
|
93
|
+
expect(schema.safeParse({ [BigFish.Tuna]: "asdf", [BigFish.Salmon]: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
|
|
94
|
+
{
|
|
95
|
+
"error": [ZodError: [
|
|
96
|
+
{
|
|
97
|
+
"code": "unrecognized_keys",
|
|
98
|
+
"keys": [
|
|
99
|
+
"Trout"
|
|
100
|
+
],
|
|
101
|
+
"path": [],
|
|
102
|
+
"message": "Unrecognized key: \\"Trout\\""
|
|
103
|
+
}
|
|
104
|
+
]],
|
|
105
|
+
"success": false,
|
|
106
|
+
}
|
|
107
|
+
`);
|
|
108
|
+
expect(schema.safeParse({ [BigFish.Tuna]: "asdf" })).toMatchInlineSnapshot(`
|
|
109
|
+
{
|
|
110
|
+
"error": [ZodError: [
|
|
111
|
+
{
|
|
112
|
+
"expected": "string",
|
|
113
|
+
"code": "invalid_type",
|
|
114
|
+
"path": [
|
|
115
|
+
"Shark"
|
|
116
|
+
],
|
|
117
|
+
"message": "Invalid input: expected string, received undefined"
|
|
118
|
+
}
|
|
119
|
+
]],
|
|
120
|
+
"success": false,
|
|
121
|
+
}
|
|
122
|
+
`);
|
|
123
|
+
expect(schema.safeParse({ [BigFish.Salmon]: "asdf" })).toMatchInlineSnapshot(`
|
|
124
|
+
{
|
|
125
|
+
"error": [ZodError: [
|
|
126
|
+
{
|
|
127
|
+
"expected": "string",
|
|
128
|
+
"code": "invalid_type",
|
|
129
|
+
"path": [
|
|
130
|
+
0
|
|
131
|
+
],
|
|
132
|
+
"message": "Invalid input: expected string, received undefined"
|
|
133
|
+
}
|
|
134
|
+
]],
|
|
135
|
+
"success": false,
|
|
136
|
+
}
|
|
137
|
+
`);
|
|
138
|
+
});
|
|
139
|
+
|
|
67
140
|
test("literal exhaustiveness", () => {
|
|
68
|
-
const schema = z.record(z.literal(["Tuna", "Salmon"]), z.string());
|
|
141
|
+
const schema = z.record(z.literal(["Tuna", "Salmon", 21]), z.string());
|
|
69
142
|
schema.parse({
|
|
70
143
|
Tuna: "asdf",
|
|
71
144
|
Salmon: "asdf",
|
|
145
|
+
21: "asdf",
|
|
72
146
|
});
|
|
73
147
|
|
|
74
|
-
expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
|
|
148
|
+
expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", 21: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
|
|
75
149
|
{
|
|
76
150
|
"error": [ZodError: [
|
|
77
151
|
{
|
|
@@ -96,6 +170,14 @@ test("literal exhaustiveness", () => {
|
|
|
96
170
|
"Salmon"
|
|
97
171
|
],
|
|
98
172
|
"message": "Invalid input: expected string, received undefined"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"expected": "string",
|
|
176
|
+
"code": "invalid_type",
|
|
177
|
+
"path": [
|
|
178
|
+
21
|
|
179
|
+
],
|
|
180
|
+
"message": "Invalid input: expected string, received undefined"
|
|
99
181
|
}
|
|
100
182
|
]],
|
|
101
183
|
"success": false,
|
|
@@ -143,13 +225,14 @@ test("pipe exhaustiveness", () => {
|
|
|
143
225
|
});
|
|
144
226
|
|
|
145
227
|
test("union exhaustiveness", () => {
|
|
146
|
-
const schema = z.record(z.union([z.literal("Tuna"), z.literal("Salmon")]), z.string());
|
|
147
|
-
expect(schema.parse({ Tuna: "asdf", Salmon: "asdf" })).toEqual({
|
|
228
|
+
const schema = z.record(z.union([z.literal("Tuna"), z.literal("Salmon"), z.literal(21)]), z.string());
|
|
229
|
+
expect(schema.parse({ Tuna: "asdf", Salmon: "asdf", 21: "asdf" })).toEqual({
|
|
148
230
|
Tuna: "asdf",
|
|
149
231
|
Salmon: "asdf",
|
|
232
|
+
21: "asdf",
|
|
150
233
|
});
|
|
151
234
|
|
|
152
|
-
expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
|
|
235
|
+
expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", 21: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
|
|
153
236
|
{
|
|
154
237
|
"error": [ZodError: [
|
|
155
238
|
{
|
|
@@ -174,6 +257,14 @@ test("union exhaustiveness", () => {
|
|
|
174
257
|
"Salmon"
|
|
175
258
|
],
|
|
176
259
|
"message": "Invalid input: expected string, received undefined"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"expected": "string",
|
|
263
|
+
"code": "invalid_type",
|
|
264
|
+
"path": [
|
|
265
|
+
21
|
|
266
|
+
],
|
|
267
|
+
"message": "Invalid input: expected string, received undefined"
|
|
177
268
|
}
|
|
178
269
|
]],
|
|
179
270
|
"success": false,
|
|
@@ -354,3 +445,44 @@ test("partial record", () => {
|
|
|
354
445
|
|
|
355
446
|
expect(Person.def.keyType._zod.def.type).toEqual("enum");
|
|
356
447
|
});
|
|
448
|
+
|
|
449
|
+
test("partialRecord with z.literal([key, ...])", () => {
|
|
450
|
+
const Keys = z.literal(["id", "name", "email"]);
|
|
451
|
+
const schema = z.partialRecord(Keys, z.string());
|
|
452
|
+
type Schema = z.infer<typeof schema>;
|
|
453
|
+
expectTypeOf<Schema>().toEqualTypeOf<Partial<Record<"id" | "name" | "email", string>>>();
|
|
454
|
+
|
|
455
|
+
// Should parse valid partials
|
|
456
|
+
expect(schema.parse({})).toEqual({});
|
|
457
|
+
expect(schema.parse({ id: "1" })).toEqual({ id: "1" });
|
|
458
|
+
expect(schema.parse({ name: "n", email: "e@example.com" })).toEqual({ name: "n", email: "e@example.com" });
|
|
459
|
+
|
|
460
|
+
// Should fail with unrecognized key, error checked via inline snapshot
|
|
461
|
+
expect(schema.safeParse({ foo: "bar" })).toMatchInlineSnapshot(`
|
|
462
|
+
{
|
|
463
|
+
"error": [ZodError: [
|
|
464
|
+
{
|
|
465
|
+
"code": "invalid_key",
|
|
466
|
+
"origin": "record",
|
|
467
|
+
"issues": [
|
|
468
|
+
{
|
|
469
|
+
"code": "invalid_value",
|
|
470
|
+
"values": [
|
|
471
|
+
"id",
|
|
472
|
+
"name",
|
|
473
|
+
"email"
|
|
474
|
+
],
|
|
475
|
+
"path": [],
|
|
476
|
+
"message": "Invalid option: expected one of \\"id\\"|\\"name\\"|\\"email\\""
|
|
477
|
+
}
|
|
478
|
+
],
|
|
479
|
+
"path": [
|
|
480
|
+
"foo"
|
|
481
|
+
],
|
|
482
|
+
"message": "Invalid key in record"
|
|
483
|
+
}
|
|
484
|
+
]],
|
|
485
|
+
"success": false,
|
|
486
|
+
}
|
|
487
|
+
`);
|
|
488
|
+
});
|
|
@@ -19,6 +19,10 @@ test("globalRegistry", () => {
|
|
|
19
19
|
expect(z.globalRegistry.has(a)).toEqual(false);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
+
test("globalRegistry is singleton and attached to globalThis", () => {
|
|
23
|
+
expect(z.globalRegistry).toBe((globalThis as any).__zod_globalRegistry);
|
|
24
|
+
});
|
|
25
|
+
|
|
22
26
|
test("z.registry", () => {
|
|
23
27
|
const fieldRegistry = z.registry<{ name: string; description: string }>();
|
|
24
28
|
|
|
@@ -164,7 +168,7 @@ test("loose examples", () => {
|
|
|
164
168
|
});
|
|
165
169
|
});
|
|
166
170
|
|
|
167
|
-
test("function meta
|
|
171
|
+
test("function meta without replacement", () => {
|
|
168
172
|
const myReg = z.registry<{
|
|
169
173
|
defaulter: (arg: string, test: boolean) => number;
|
|
170
174
|
}>();
|
|
@@ -775,6 +775,8 @@ test("format", () => {
|
|
|
775
775
|
expect(z.string().date().format).toEqual("date");
|
|
776
776
|
expect(z.string().time().format).toEqual("time");
|
|
777
777
|
expect(z.string().duration().format).toEqual("duration");
|
|
778
|
+
|
|
779
|
+
expect(z.mac().format).toEqual("mac");
|
|
778
780
|
});
|
|
779
781
|
|
|
780
782
|
test("min max getters", () => {
|
|
@@ -801,6 +803,24 @@ test("lowerCase", () => {
|
|
|
801
803
|
expect(z.string().toUpperCase().parse("asdf")).toEqual("ASDF");
|
|
802
804
|
});
|
|
803
805
|
|
|
806
|
+
test("slugify", () => {
|
|
807
|
+
expect(z.string().slugify().parse("Hello World")).toEqual("hello-world");
|
|
808
|
+
expect(z.string().slugify().parse(" Hello World ")).toEqual("hello-world");
|
|
809
|
+
expect(z.string().slugify().parse("Hello@World#123")).toEqual("helloworld123");
|
|
810
|
+
expect(z.string().slugify().parse("Hello-World")).toEqual("hello-world");
|
|
811
|
+
expect(z.string().slugify().parse("Hello_World")).toEqual("hello-world");
|
|
812
|
+
expect(z.string().slugify().parse("---Hello---World---")).toEqual("hello-world");
|
|
813
|
+
expect(z.string().slugify().parse("Hello World")).toEqual("hello-world");
|
|
814
|
+
expect(z.string().slugify().parse("Hello!@#$%^&*()World")).toEqual("helloworld");
|
|
815
|
+
|
|
816
|
+
// can be used with check
|
|
817
|
+
expect(z.string().check(z.slugify()).parse("Hello World")).toEqual("hello-world");
|
|
818
|
+
|
|
819
|
+
// can be chained with other methods
|
|
820
|
+
expect(z.string().slugify().min(5).parse("Hello World")).toEqual("hello-world");
|
|
821
|
+
expect(() => z.string().slugify().min(20).parse("Hello World")).toThrow();
|
|
822
|
+
});
|
|
823
|
+
|
|
804
824
|
// test("IP validation", () => {
|
|
805
825
|
// const ipSchema = z.string().ip();
|
|
806
826
|
|
|
@@ -885,6 +905,58 @@ test("IPv6 validation", () => {
|
|
|
885
905
|
expect(() => ipv6.parse("254.164.77.1")).toThrow();
|
|
886
906
|
});
|
|
887
907
|
|
|
908
|
+
test("MAC validation", () => {
|
|
909
|
+
const mac = z.mac();
|
|
910
|
+
|
|
911
|
+
// Valid MAC addresses
|
|
912
|
+
expect(mac.safeParse("00:1A:2B:3C:4D:5E").success).toBe(true);
|
|
913
|
+
expect(mac.safeParse("FF:FF:FF:FF:FF:FF").success).toBe(true);
|
|
914
|
+
expect(mac.safeParse("00:11:22:33:44:55").success).toBe(true);
|
|
915
|
+
expect(mac.safeParse("A1:B2:C3:D4:E5:F6").success).toBe(true);
|
|
916
|
+
expect(mac.safeParse("10:20:30:40:50:60").success).toBe(true);
|
|
917
|
+
expect(mac.safeParse("0a:1b:2c:3d:4e:5f").success).toBe(true);
|
|
918
|
+
expect(mac.safeParse("12:34:56:78:9A:BC").success).toBe(true);
|
|
919
|
+
|
|
920
|
+
// Invalid MAC addresses
|
|
921
|
+
expect(mac.safeParse("00:1A-2B:3C-4D:5E").success).toBe(false);
|
|
922
|
+
expect(mac.safeParse("00:1A:2B:3C:4D").success).toBe(false);
|
|
923
|
+
expect(mac.safeParse("00:1A:2B:3C:4D").success).toBe(false);
|
|
924
|
+
expect(mac.safeParse("00-1A-2B-3C-4D").success).toBe(false);
|
|
925
|
+
expect(mac.safeParse("01-23-45-67-89-AB").success).toBe(false); // Dash delimiter not accepted by default
|
|
926
|
+
expect(mac.safeParse("AA-BB-CC-DD-EE-FF").success).toBe(false); // Dash delimiter not accepted by default
|
|
927
|
+
expect(mac.safeParse("DE-AD-BE-EF-00-01").success).toBe(false); // Dash delimiter not accepted by default
|
|
928
|
+
expect(mac.safeParse("98-76-54-32-10-FF").success).toBe(false); // Dash delimiter not accepted by default
|
|
929
|
+
expect(mac.safeParse("00:1A:2B:3C:4D:GZ").success).toBe(false);
|
|
930
|
+
expect(mac.safeParse("00:1A:2B:3C:4D:5E:GG").success).toBe(false);
|
|
931
|
+
expect(mac.safeParse("123:45:67:89:AB:CD").success).toBe(false);
|
|
932
|
+
expect(mac.safeParse("00--1A:2B:3C:4D:5E").success).toBe(false);
|
|
933
|
+
expect(mac.safeParse("00:1A::2B:3C:4D:5E").success).toBe(false);
|
|
934
|
+
expect(mac.safeParse("00:1A:2B:3C:3C:2B:1A:00").success).toBe(false); // Disallow EUI-64
|
|
935
|
+
expect(mac.safeParse("00:1a:2B:3c:4D:5e").success).toBe(false); // Disallow mixed-case
|
|
936
|
+
|
|
937
|
+
// MAC formats that are nonstandard but occassionally referenced, ex. https://www.postgresql.org/docs/17/datatype-net-types.html#DATATYPE-MACADDR
|
|
938
|
+
expect(mac.safeParse("00:1A:2B:3C:4D:5E:FF").success).toBe(false);
|
|
939
|
+
expect(mac.safeParse("001A2B:3C4D5E").success).toBe(false);
|
|
940
|
+
expect(mac.safeParse("001A:2B3C:4D5E").success).toBe(false);
|
|
941
|
+
expect(mac.safeParse("001A.2B3C.4D5E").success).toBe(false);
|
|
942
|
+
expect(mac.safeParse("001A2B3C4D5E").success).toBe(false);
|
|
943
|
+
expect(mac.safeParse("00.1A.2B.3C.4D.5E").success).toBe(false);
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
test("MAC validation with custom delimiter", () => {
|
|
947
|
+
const colonMac = z.mac({ delimiter: ":" });
|
|
948
|
+
expect(colonMac.safeParse("00:1A:2B:3C:4D:5E").success).toBe(true);
|
|
949
|
+
expect(colonMac.safeParse("00-1A-2B-3C-4D-5E").success).toBe(false);
|
|
950
|
+
|
|
951
|
+
const dashMac = z.mac({ delimiter: "-" });
|
|
952
|
+
expect(dashMac.safeParse("00-1A-2B-3C-4D-5E").success).toBe(true);
|
|
953
|
+
expect(dashMac.safeParse("00:1A:2B:3C:4D:5E").success).toBe(false);
|
|
954
|
+
|
|
955
|
+
const colonOnlyMac = z.mac({ delimiter: ":" });
|
|
956
|
+
expect(colonOnlyMac.safeParse("00:1A:2B:3C:4D:5E").success).toBe(true);
|
|
957
|
+
expect(colonOnlyMac.safeParse("00-1A-2B-3C-4D-5E").success).toBe(false);
|
|
958
|
+
});
|
|
959
|
+
|
|
888
960
|
test("CIDR v4 validation", () => {
|
|
889
961
|
const cidrV4 = z.string().cidrv4();
|
|
890
962
|
|
|
@@ -43,6 +43,7 @@ const email = z.templateLiteral(["", z.string().email()]);
|
|
|
43
43
|
// const ip = z.templateLiteral(["", z.string().ip()]);
|
|
44
44
|
const ipv4 = z.templateLiteral(["", z.string().ipv4()]);
|
|
45
45
|
const ipv6 = z.templateLiteral(["", z.string().ipv6()]);
|
|
46
|
+
const mac = z.templateLiteral(["", z.mac()]);
|
|
46
47
|
const ulid = z.templateLiteral(["", z.string().ulid()]);
|
|
47
48
|
const uuid = z.templateLiteral(["", z.string().uuid()]);
|
|
48
49
|
const stringAToZ = z.templateLiteral(["", z.string().regex(/^[a-z]+$/)]);
|
|
@@ -137,6 +138,7 @@ test("template literal type inference", () => {
|
|
|
137
138
|
// expectTypeOf<z.infer<typeof ip>>().toEqualTypeOf<string>();
|
|
138
139
|
expectTypeOf<z.infer<typeof ipv4>>().toEqualTypeOf<string>();
|
|
139
140
|
expectTypeOf<z.infer<typeof ipv6>>().toEqualTypeOf<string>();
|
|
141
|
+
expectTypeOf<z.infer<typeof mac>>().toEqualTypeOf<string>();
|
|
140
142
|
expectTypeOf<z.infer<typeof ulid>>().toEqualTypeOf<string>();
|
|
141
143
|
expectTypeOf<z.infer<typeof uuid>>().toEqualTypeOf<string>();
|
|
142
144
|
expectTypeOf<z.infer<typeof stringAToZ>>().toEqualTypeOf<string>();
|
|
@@ -361,6 +363,7 @@ test("template literal parsing - success - basic cases", () => {
|
|
|
361
363
|
// ip.parse("c359:f57c:21e5:39eb:1187:e501:f936:b452");
|
|
362
364
|
ipv4.parse("213.174.246.205");
|
|
363
365
|
ipv6.parse("c359:f57c:21e5:39eb:1187:e501:f936:b452");
|
|
366
|
+
mac.parse("00:1A:2B:3C:4D:5E");
|
|
364
367
|
ulid.parse("01GW3D2QZJBYB6P1Z1AE997VPW");
|
|
365
368
|
uuid.parse("808989fd-3a6e-4af2-b607-737323a176f6");
|
|
366
369
|
stringAToZ.parse("asudgaskhdgashd");
|
|
@@ -497,6 +500,8 @@ test("template literal parsing - failure - basic cases", () => {
|
|
|
497
500
|
expect(() => ipv4.parse("c359:f57c:21e5:39eb:1187:e501:f936:b452")).toThrow();
|
|
498
501
|
expect(() => ipv6.parse("c359:f57c:21e5:39eb:1187:e501:f936:b4521")).toThrow();
|
|
499
502
|
expect(() => ipv6.parse("213.174.246.205")).toThrow();
|
|
503
|
+
expect(() => mac.parse("00:1A:2B:3C:4D:5E:6A:7B")).toThrow();
|
|
504
|
+
expect(() => mac.parse("00:1A:2B:3C")).toThrow();
|
|
500
505
|
expect(() => ulid.parse("01GW3D2QZJBYB6P1Z1AE997VPW!")).toThrow();
|
|
501
506
|
expect(() => uuid.parse("808989fd-3a6e-4af2-b607-737323a176f6Z")).toThrow();
|
|
502
507
|
expect(() => uuid.parse("Z808989fd-3a6e-4af2-b607-737323a176f6")).toThrow();
|
|
@@ -568,6 +573,9 @@ test("regexes", () => {
|
|
|
568
573
|
expect(ipv6._zod.pattern.source).toMatchInlineSnapshot(
|
|
569
574
|
`"^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$"`
|
|
570
575
|
);
|
|
576
|
+
expect(mac._zod.pattern.source).toMatchInlineSnapshot(
|
|
577
|
+
`"^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$"`
|
|
578
|
+
);
|
|
571
579
|
expect(ulid._zod.pattern.source).toMatchInlineSnapshot(`"^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$"`);
|
|
572
580
|
expect(uuid._zod.pattern.source).toMatchInlineSnapshot(
|
|
573
581
|
`"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"`
|