zod 3.26.0-canary.20250703T013930 → 3.26.0-canary.20250703T214020
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 +20 -20
- package/src/index.ts +4 -0
- package/src/v3/ZodError.ts +330 -0
- package/src/v3/benchmarks/datetime.ts +58 -0
- package/src/v3/benchmarks/discriminatedUnion.ts +80 -0
- package/src/v3/benchmarks/index.ts +59 -0
- package/src/v3/benchmarks/ipv4.ts +57 -0
- package/src/v3/benchmarks/object.ts +69 -0
- package/src/v3/benchmarks/primitives.ts +162 -0
- package/src/v3/benchmarks/realworld.ts +63 -0
- package/src/v3/benchmarks/string.ts +55 -0
- package/src/v3/benchmarks/union.ts +80 -0
- package/src/v3/errors.ts +13 -0
- package/src/v3/external.ts +6 -0
- package/src/v3/helpers/enumUtil.ts +17 -0
- package/src/v3/helpers/errorUtil.ts +8 -0
- package/src/v3/helpers/parseUtil.ts +176 -0
- package/src/v3/helpers/partialUtil.ts +34 -0
- package/src/v3/helpers/typeAliases.ts +2 -0
- package/src/v3/helpers/util.ts +224 -0
- package/src/v3/index.ts +4 -0
- package/src/v3/locales/en.ts +124 -0
- package/src/v3/standard-schema.ts +113 -0
- package/src/v3/tests/Mocker.ts +54 -0
- package/src/v3/tests/all-errors.test.ts +157 -0
- package/src/v3/tests/anyunknown.test.ts +28 -0
- package/src/v3/tests/array.test.ts +71 -0
- package/src/v3/tests/async-parsing.test.ts +388 -0
- package/src/v3/tests/async-refinements.test.ts +46 -0
- package/src/v3/tests/base.test.ts +29 -0
- package/src/v3/tests/bigint.test.ts +55 -0
- package/src/v3/tests/branded.test.ts +53 -0
- package/src/v3/tests/catch.test.ts +220 -0
- package/src/v3/tests/coerce.test.ts +133 -0
- package/src/v3/tests/complex.test.ts +56 -0
- package/src/v3/tests/custom.test.ts +31 -0
- package/src/v3/tests/date.test.ts +32 -0
- package/src/v3/tests/deepmasking.test.ts +186 -0
- package/src/v3/tests/default.test.ts +112 -0
- package/src/v3/tests/description.test.ts +33 -0
- package/src/v3/tests/discriminated-unions.test.ts +315 -0
- package/src/v3/tests/enum.test.ts +80 -0
- package/src/v3/tests/error.test.ts +551 -0
- package/src/v3/tests/firstparty.test.ts +87 -0
- package/src/v3/tests/firstpartyschematypes.test.ts +21 -0
- package/src/v3/tests/function.test.ts +257 -0
- package/src/v3/tests/generics.test.ts +48 -0
- package/src/v3/tests/instanceof.test.ts +37 -0
- package/src/v3/tests/intersection.test.ts +110 -0
- package/src/v3/tests/language-server.source.ts +76 -0
- package/src/v3/tests/language-server.test.ts +207 -0
- package/src/v3/tests/literal.test.ts +36 -0
- package/src/v3/tests/map.test.ts +110 -0
- package/src/v3/tests/masking.test.ts +4 -0
- package/src/v3/tests/mocker.test.ts +19 -0
- package/src/v3/tests/nan.test.ts +21 -0
- package/src/v3/tests/nativeEnum.test.ts +87 -0
- package/src/v3/tests/nullable.test.ts +42 -0
- package/src/v3/tests/number.test.ts +176 -0
- package/src/v3/tests/object-augmentation.test.ts +29 -0
- package/src/v3/tests/object-in-es5-env.test.ts +29 -0
- package/src/v3/tests/object.test.ts +434 -0
- package/src/v3/tests/optional.test.ts +42 -0
- package/src/v3/tests/parseUtil.test.ts +23 -0
- package/src/v3/tests/parser.test.ts +41 -0
- package/src/v3/tests/partials.test.ts +243 -0
- package/src/v3/tests/pickomit.test.ts +111 -0
- package/src/v3/tests/pipeline.test.ts +29 -0
- package/src/v3/tests/preprocess.test.ts +186 -0
- package/src/v3/tests/primitive.test.ts +440 -0
- package/src/v3/tests/promise.test.ts +90 -0
- package/src/v3/tests/readonly.test.ts +194 -0
- package/src/v3/tests/record.test.ts +171 -0
- package/src/v3/tests/recursive.test.ts +197 -0
- package/src/v3/tests/refine.test.ts +313 -0
- package/src/v3/tests/safeparse.test.ts +27 -0
- package/src/v3/tests/set.test.ts +142 -0
- package/src/v3/tests/standard-schema.test.ts +83 -0
- package/src/v3/tests/string.test.ts +916 -0
- package/src/v3/tests/transformer.test.ts +233 -0
- package/src/v3/tests/tuple.test.ts +90 -0
- package/src/v3/tests/unions.test.ts +57 -0
- package/src/v3/tests/validations.test.ts +133 -0
- package/src/v3/tests/void.test.ts +15 -0
- package/src/v3/types.ts +5136 -0
- package/src/v4/classic/checks.ts +30 -0
- package/src/v4/classic/coerce.ts +27 -0
- package/src/v4/classic/compat.ts +66 -0
- package/src/v4/classic/errors.ts +75 -0
- package/src/v4/classic/external.ts +50 -0
- package/src/v4/classic/index.ts +5 -0
- package/src/v4/classic/iso.ts +90 -0
- package/src/v4/classic/parse.ts +33 -0
- package/src/v4/classic/schemas.ts +2055 -0
- package/src/v4/classic/tests/anyunknown.test.ts +26 -0
- package/src/v4/classic/tests/array.test.ts +264 -0
- package/src/v4/classic/tests/assignability.test.ts +210 -0
- package/src/v4/classic/tests/async-parsing.test.ts +381 -0
- package/src/v4/classic/tests/async-refinements.test.ts +68 -0
- package/src/v4/classic/tests/base.test.ts +7 -0
- package/src/v4/classic/tests/bigint.test.ts +54 -0
- package/src/v4/classic/tests/brand.test.ts +65 -0
- package/src/v4/classic/tests/catch.test.ts +252 -0
- package/src/v4/classic/tests/coalesce.test.ts +20 -0
- package/src/v4/classic/tests/coerce.test.ts +160 -0
- package/src/v4/classic/tests/continuability.test.ts +352 -0
- package/src/v4/classic/tests/custom.test.ts +40 -0
- package/src/v4/classic/tests/date.test.ts +31 -0
- package/src/v4/classic/tests/datetime.test.ts +296 -0
- package/src/v4/classic/tests/default.test.ts +313 -0
- package/src/v4/classic/tests/description.test.ts +32 -0
- package/src/v4/classic/tests/discriminated-unions.test.ts +592 -0
- package/src/v4/classic/tests/enum.test.ts +285 -0
- package/src/v4/classic/tests/error-utils.test.ts +527 -0
- package/src/v4/classic/tests/error.test.ts +711 -0
- package/src/v4/classic/tests/file.test.ts +91 -0
- package/src/v4/classic/tests/firstparty.test.ts +175 -0
- package/src/v4/classic/tests/function.test.ts +268 -0
- package/src/v4/classic/tests/generics.test.ts +72 -0
- package/src/v4/classic/tests/index.test.ts +829 -0
- package/src/v4/classic/tests/instanceof.test.ts +34 -0
- package/src/v4/classic/tests/intersection.test.ts +171 -0
- package/src/v4/classic/tests/json.test.ts +108 -0
- package/src/v4/classic/tests/lazy.test.ts +227 -0
- package/src/v4/classic/tests/literal.test.ts +92 -0
- package/src/v4/classic/tests/map.test.ts +196 -0
- package/src/v4/classic/tests/nan.test.ts +21 -0
- package/src/v4/classic/tests/nested-refine.test.ts +168 -0
- package/src/v4/classic/tests/nonoptional.test.ts +86 -0
- package/src/v4/classic/tests/nullable.test.ts +22 -0
- package/src/v4/classic/tests/number.test.ts +247 -0
- package/src/v4/classic/tests/object.test.ts +553 -0
- package/src/v4/classic/tests/optional.test.ts +103 -0
- package/src/v4/classic/tests/partial.test.ts +147 -0
- package/src/v4/classic/tests/pickomit.test.ts +127 -0
- package/src/v4/classic/tests/pipe.test.ts +81 -0
- package/src/v4/classic/tests/prefault.test.ts +37 -0
- package/src/v4/classic/tests/preprocess.test.ts +298 -0
- package/src/v4/classic/tests/primitive.test.ts +175 -0
- package/src/v4/classic/tests/promise.test.ts +81 -0
- package/src/v4/classic/tests/prototypes.test.ts +23 -0
- package/src/v4/classic/tests/readonly.test.ts +252 -0
- package/src/v4/classic/tests/record.test.ts +332 -0
- package/src/v4/classic/tests/recursive-types.test.ts +325 -0
- package/src/v4/classic/tests/refine.test.ts +423 -0
- package/src/v4/classic/tests/registries.test.ts +195 -0
- package/src/v4/classic/tests/set.test.ts +179 -0
- package/src/v4/classic/tests/standard-schema.test.ts +57 -0
- package/src/v4/classic/tests/string-formats.test.ts +109 -0
- package/src/v4/classic/tests/string.test.ts +881 -0
- package/src/v4/classic/tests/stringbool.test.ts +66 -0
- package/src/v4/classic/tests/template-literal.test.ts +758 -0
- package/src/v4/classic/tests/to-json-schema.test.ts +2182 -0
- package/src/v4/classic/tests/transform.test.ts +250 -0
- package/src/v4/classic/tests/tuple.test.ts +163 -0
- package/src/v4/classic/tests/union.test.ts +94 -0
- package/src/v4/classic/tests/validations.test.ts +283 -0
- package/src/v4/classic/tests/void.test.ts +12 -0
- package/src/v4/core/api.ts +1592 -0
- package/src/v4/core/checks.ts +1285 -0
- package/src/v4/core/config.ts +15 -0
- package/src/v4/core/core.ts +134 -0
- package/src/v4/core/doc.ts +44 -0
- package/src/v4/core/errors.ts +420 -0
- package/src/v4/core/function.ts +176 -0
- package/src/v4/core/index.ts +15 -0
- package/src/v4/core/json-schema.ts +143 -0
- package/src/v4/core/parse.ts +94 -0
- package/src/v4/core/regexes.ts +135 -0
- package/src/v4/core/registries.ts +86 -0
- package/src/v4/core/schemas.ts +3781 -0
- package/src/v4/core/standard-schema.ts +64 -0
- package/src/v4/core/tests/index.test.ts +46 -0
- package/src/v4/core/tests/locales/be.test.ts +124 -0
- package/src/v4/core/tests/locales/en.test.ts +22 -0
- package/src/v4/core/tests/locales/ru.test.ts +128 -0
- package/src/v4/core/tests/locales/tr.test.ts +69 -0
- package/src/v4/core/to-json-schema.ts +944 -0
- package/src/v4/core/util.ts +775 -0
- package/src/v4/core/versions.ts +5 -0
- package/src/v4/core/zsf.ts +323 -0
- package/src/v4/index.ts +4 -0
- package/src/v4/locales/ar.ts +125 -0
- package/src/v4/locales/az.ts +121 -0
- package/src/v4/locales/be.ts +184 -0
- package/src/v4/locales/ca.ts +127 -0
- package/src/v4/locales/cs.ts +142 -0
- package/src/v4/locales/de.ts +124 -0
- package/src/v4/locales/en.ts +127 -0
- package/src/v4/locales/eo.ts +125 -0
- package/src/v4/locales/es.ts +125 -0
- package/src/v4/locales/fa.ts +134 -0
- package/src/v4/locales/fi.ts +131 -0
- package/src/v4/locales/fr-CA.ts +126 -0
- package/src/v4/locales/fr.ts +124 -0
- package/src/v4/locales/he.ts +125 -0
- package/src/v4/locales/hu.ts +126 -0
- package/src/v4/locales/id.ts +125 -0
- package/src/v4/locales/index.ts +39 -0
- package/src/v4/locales/it.ts +125 -0
- package/src/v4/locales/ja.ts +122 -0
- package/src/v4/locales/kh.ts +126 -0
- package/src/v4/locales/ko.ts +131 -0
- package/src/v4/locales/mk.ts +127 -0
- package/src/v4/locales/ms.ts +124 -0
- package/src/v4/locales/nl.ts +126 -0
- package/src/v4/locales/no.ts +124 -0
- package/src/v4/locales/ota.ts +125 -0
- package/src/v4/locales/pl.ts +126 -0
- package/src/v4/locales/ps.ts +133 -0
- package/src/v4/locales/pt.ts +123 -0
- package/src/v4/locales/ru.ts +184 -0
- package/src/v4/locales/sl.ts +126 -0
- package/src/v4/locales/sv.ts +127 -0
- package/src/v4/locales/ta.ts +125 -0
- package/src/v4/locales/th.ts +126 -0
- package/src/v4/locales/tr.ts +121 -0
- package/src/v4/locales/ua.ts +126 -0
- package/src/v4/locales/ur.ts +126 -0
- package/src/v4/locales/vi.ts +125 -0
- package/src/v4/locales/zh-CN.ts +123 -0
- package/src/v4/locales/zh-TW.ts +125 -0
- package/src/v4/mini/checks.ts +32 -0
- package/src/v4/mini/coerce.ts +22 -0
- package/src/v4/mini/external.ts +40 -0
- package/src/v4/mini/index.ts +3 -0
- package/src/v4/mini/iso.ts +62 -0
- package/src/v4/mini/parse.ts +1 -0
- package/src/v4/mini/schemas.ts +1579 -0
- package/src/v4/mini/tests/assignability.test.ts +129 -0
- package/src/v4/mini/tests/brand.test.ts +51 -0
- package/src/v4/mini/tests/checks.test.ts +144 -0
- package/src/v4/mini/tests/computed.test.ts +36 -0
- package/src/v4/mini/tests/error.test.ts +22 -0
- package/src/v4/mini/tests/functions.test.ts +43 -0
- package/src/v4/mini/tests/index.test.ts +871 -0
- package/src/v4/mini/tests/number.test.ts +95 -0
- package/src/v4/mini/tests/object.test.ts +185 -0
- package/src/v4/mini/tests/prototypes.test.ts +43 -0
- package/src/v4/mini/tests/recursive-types.test.ts +275 -0
- package/src/v4/mini/tests/string.test.ts +293 -0
- package/src/v4-mini/index.ts +1 -0
- package/v4/classic/compat.cjs +1 -7
- package/v4/classic/compat.d.cts +0 -2
- package/v4/classic/compat.d.ts +0 -2
- package/v4/classic/compat.js +0 -6
- package/v4/classic/external.cjs +2 -1
- package/v4/classic/external.d.cts +1 -1
- package/v4/classic/external.d.ts +1 -1
- package/v4/classic/external.js +1 -1
- package/v4/classic/schemas.d.cts +4 -4
- package/v4/classic/schemas.d.ts +4 -4
- package/v4/core/api.cjs +11 -10
- package/v4/core/api.js +11 -10
- package/v4/core/checks.cjs +6 -4
- package/v4/core/checks.js +6 -4
- package/v4/core/core.cjs +5 -1
- package/v4/core/core.d.cts +2 -0
- package/v4/core/core.d.ts +2 -0
- package/v4/core/core.js +4 -0
- package/v4/core/schemas.cjs +12 -13
- package/v4/core/schemas.js +12 -13
- package/v4/core/util.cjs +3 -0
- package/v4/core/util.d.cts +1 -1
- package/v4/core/util.d.ts +1 -1
- package/v4/core/util.js +3 -0
- package/v4/locales/eo.cjs +144 -0
- package/v4/locales/eo.d.cts +5 -0
- package/v4/locales/eo.d.ts +5 -0
- package/v4/locales/eo.js +116 -0
- package/v4/locales/index.cjs +3 -1
- package/v4/locales/index.d.cts +1 -0
- package/v4/locales/index.d.ts +1 -0
- package/v4/locales/index.js +1 -0
- package/v4/mini/external.cjs +2 -1
- package/v4/mini/external.d.cts +1 -1
- package/v4/mini/external.d.ts +1 -1
- package/v4/mini/external.js +1 -1
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import * as z from "zod/v4";
|
|
3
|
+
|
|
4
|
+
describe("basic refinement functionality", () => {
|
|
5
|
+
test("should create a new schema instance when refining", () => {
|
|
6
|
+
const obj1 = z.object({
|
|
7
|
+
first: z.string(),
|
|
8
|
+
second: z.string(),
|
|
9
|
+
});
|
|
10
|
+
const obj2 = obj1.partial().strict();
|
|
11
|
+
const obj3 = obj2.refine((data) => data.first || data.second, "Either first or second should be filled in.");
|
|
12
|
+
|
|
13
|
+
expect(obj1 === (obj2 as any)).toEqual(false);
|
|
14
|
+
expect(obj2 === (obj3 as any)).toEqual(false);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("should validate according to refinement logic", () => {
|
|
18
|
+
const schema = z
|
|
19
|
+
.object({
|
|
20
|
+
first: z.string(),
|
|
21
|
+
second: z.string(),
|
|
22
|
+
})
|
|
23
|
+
.partial()
|
|
24
|
+
.strict()
|
|
25
|
+
.refine((data) => data.first || data.second, "Either first or second should be filled in.");
|
|
26
|
+
|
|
27
|
+
// Should fail on empty object
|
|
28
|
+
expect(() => schema.parse({})).toThrow();
|
|
29
|
+
|
|
30
|
+
// Should pass with first property
|
|
31
|
+
expect(schema.parse({ first: "a" })).toEqual({ first: "a" });
|
|
32
|
+
|
|
33
|
+
// Should pass with second property
|
|
34
|
+
expect(schema.parse({ second: "a" })).toEqual({ second: "a" });
|
|
35
|
+
|
|
36
|
+
// Should pass with both properties
|
|
37
|
+
expect(schema.parse({ first: "a", second: "a" })).toEqual({ first: "a", second: "a" });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("should validate strict mode correctly", () => {
|
|
41
|
+
const schema = z
|
|
42
|
+
.object({
|
|
43
|
+
first: z.string(),
|
|
44
|
+
second: z.string(),
|
|
45
|
+
})
|
|
46
|
+
.partial()
|
|
47
|
+
.strict();
|
|
48
|
+
|
|
49
|
+
// Should throw on extra properties
|
|
50
|
+
expect(() => schema.parse({ third: "adsf" })).toThrow();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("refinement with custom error messages", () => {
|
|
55
|
+
test("should use custom error message when validation fails", () => {
|
|
56
|
+
const validationSchema = z
|
|
57
|
+
.object({
|
|
58
|
+
email: z.string().email(),
|
|
59
|
+
password: z.string(),
|
|
60
|
+
confirmPassword: z.string(),
|
|
61
|
+
})
|
|
62
|
+
.refine((data) => data.password === data.confirmPassword, "Both password and confirmation must match");
|
|
63
|
+
|
|
64
|
+
const result = validationSchema.safeParse({
|
|
65
|
+
email: "aaaa@gmail.com",
|
|
66
|
+
password: "aaaaaaaa",
|
|
67
|
+
confirmPassword: "bbbbbbbb",
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(result.success).toEqual(false);
|
|
71
|
+
if (!result.success) {
|
|
72
|
+
expect(result.error.issues[0].message).toEqual("Both password and confirmation must match");
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("async refinements", () => {
|
|
78
|
+
test("should support async refinement functions", async () => {
|
|
79
|
+
const validationSchema = z
|
|
80
|
+
.object({
|
|
81
|
+
email: z.string().email(),
|
|
82
|
+
password: z.string(),
|
|
83
|
+
confirmPassword: z.string(),
|
|
84
|
+
})
|
|
85
|
+
.refine(
|
|
86
|
+
(data) => Promise.resolve().then(() => data.password === data.confirmPassword),
|
|
87
|
+
"Both password and confirmation must match"
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Should pass with matching passwords
|
|
91
|
+
const validData = {
|
|
92
|
+
email: "aaaa@gmail.com",
|
|
93
|
+
password: "password",
|
|
94
|
+
confirmPassword: "password",
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
await expect(validationSchema.parseAsync(validData)).resolves.toEqual(validData);
|
|
98
|
+
|
|
99
|
+
// Should fail with non-matching passwords
|
|
100
|
+
await expect(
|
|
101
|
+
validationSchema.parseAsync({
|
|
102
|
+
email: "aaaa@gmail.com",
|
|
103
|
+
password: "password",
|
|
104
|
+
confirmPassword: "different",
|
|
105
|
+
})
|
|
106
|
+
).rejects.toThrow();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("early termination options", () => {
|
|
111
|
+
test("should abort early with continue: false", () => {
|
|
112
|
+
const schema = z
|
|
113
|
+
.string()
|
|
114
|
+
.superRefine((val, ctx) => {
|
|
115
|
+
if (val.length < 2) {
|
|
116
|
+
ctx.addIssue({
|
|
117
|
+
code: "custom",
|
|
118
|
+
message: "BAD",
|
|
119
|
+
continue: false,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
.refine((_) => false);
|
|
124
|
+
|
|
125
|
+
const result = schema.safeParse("");
|
|
126
|
+
expect(result.success).toEqual(false);
|
|
127
|
+
if (!result.success) {
|
|
128
|
+
expect(result.error.issues.length).toEqual(1);
|
|
129
|
+
expect(result.error.issues[0].message).toEqual("BAD");
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("should abort early with fatal: true", () => {
|
|
134
|
+
const schema = z
|
|
135
|
+
.string()
|
|
136
|
+
.superRefine((val, ctx) => {
|
|
137
|
+
if (val.length < 2) {
|
|
138
|
+
ctx.addIssue({
|
|
139
|
+
code: "custom",
|
|
140
|
+
fatal: true,
|
|
141
|
+
message: "BAD",
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
.refine((_) => false);
|
|
146
|
+
|
|
147
|
+
const result = schema.safeParse("");
|
|
148
|
+
expect(result.success).toEqual(false);
|
|
149
|
+
if (!result.success) {
|
|
150
|
+
expect(result.error.issues.length).toEqual(1);
|
|
151
|
+
expect(result.error.issues[0].message).toEqual("BAD");
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("should abort early with abort flag", () => {
|
|
156
|
+
const schema = z
|
|
157
|
+
.string()
|
|
158
|
+
.refine((_) => false, { abort: true })
|
|
159
|
+
.refine((_) => false);
|
|
160
|
+
|
|
161
|
+
const result = schema.safeParse("");
|
|
162
|
+
expect(result.success).toEqual(false);
|
|
163
|
+
if (!result.success) {
|
|
164
|
+
expect(result.error.issues.length).toEqual(1);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("custom error paths", () => {
|
|
170
|
+
test("should use custom path in error message", async () => {
|
|
171
|
+
const result = await z
|
|
172
|
+
.object({ password: z.string(), confirm: z.string() })
|
|
173
|
+
.refine((data) => data.confirm === data.password, { path: ["confirm"] })
|
|
174
|
+
.safeParse({ password: "asdf", confirm: "qewr" });
|
|
175
|
+
|
|
176
|
+
expect(result.success).toEqual(false);
|
|
177
|
+
if (!result.success) {
|
|
178
|
+
expect(result.error.issues[0].path).toEqual(["confirm"]);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe("superRefine functionality", () => {
|
|
184
|
+
test("should support multiple validation rules", () => {
|
|
185
|
+
const Strings = z.array(z.string()).superRefine((val, ctx) => {
|
|
186
|
+
if (val.length > 3) {
|
|
187
|
+
ctx.addIssue({
|
|
188
|
+
input: val,
|
|
189
|
+
code: "too_big",
|
|
190
|
+
origin: "array",
|
|
191
|
+
maximum: 3,
|
|
192
|
+
inclusive: true,
|
|
193
|
+
exact: true,
|
|
194
|
+
message: "Too many items 😡",
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (val.length !== new Set(val).size) {
|
|
199
|
+
ctx.addIssue({
|
|
200
|
+
input: val,
|
|
201
|
+
code: "custom",
|
|
202
|
+
message: `No duplicates allowed.`,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Should fail with too many items and duplicates
|
|
208
|
+
const result = Strings.safeParse(["asfd", "asfd", "asfd", "asfd"]);
|
|
209
|
+
expect(result.success).toEqual(false);
|
|
210
|
+
if (!result.success) {
|
|
211
|
+
expect(result.error.issues.length).toEqual(2);
|
|
212
|
+
expect(result.error.issues[0].message).toEqual("Too many items 😡");
|
|
213
|
+
expect(result.error.issues[1].message).toEqual("No duplicates allowed.");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Should pass with valid input
|
|
217
|
+
const validArray = ["asfd", "qwer"];
|
|
218
|
+
expect(Strings.parse(validArray)).toEqual(validArray);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("should support async superRefine", async () => {
|
|
222
|
+
const Strings = z.array(z.string()).superRefine(async (val, ctx) => {
|
|
223
|
+
if (val.length > 3) {
|
|
224
|
+
ctx.addIssue({
|
|
225
|
+
input: val,
|
|
226
|
+
code: "too_big",
|
|
227
|
+
origin: "array",
|
|
228
|
+
maximum: 3,
|
|
229
|
+
inclusive: true,
|
|
230
|
+
message: "Too many items 😡",
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (val.length !== new Set(val).size) {
|
|
235
|
+
ctx.addIssue({
|
|
236
|
+
input: val,
|
|
237
|
+
code: "custom",
|
|
238
|
+
message: `No duplicates allowed.`,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Should fail with too many items and duplicates
|
|
244
|
+
const result = await Strings.safeParseAsync(["asfd", "asfd", "asfd", "asfd"]);
|
|
245
|
+
expect(result.success).toEqual(false);
|
|
246
|
+
if (!result.success) {
|
|
247
|
+
expect(result.error.issues.length).toEqual(2);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Should pass with valid input
|
|
251
|
+
const validArray = ["asfd", "qwer"];
|
|
252
|
+
await expect(Strings.parseAsync(validArray)).resolves.toEqual(validArray);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("should accept string as shorthand for custom error message", () => {
|
|
256
|
+
const schema = z.string().superRefine((_, ctx) => {
|
|
257
|
+
ctx.addIssue("bad stuff");
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const result = schema.safeParse("asdf");
|
|
261
|
+
expect(result.success).toEqual(false);
|
|
262
|
+
if (!result.success) {
|
|
263
|
+
expect(result.error.issues).toHaveLength(1);
|
|
264
|
+
expect(result.error.issues[0].message).toEqual("bad stuff");
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("should respect fatal flag in superRefine", () => {
|
|
269
|
+
const schema = z
|
|
270
|
+
.string()
|
|
271
|
+
.superRefine((val, ctx) => {
|
|
272
|
+
if (val === "") {
|
|
273
|
+
ctx.addIssue({
|
|
274
|
+
input: val,
|
|
275
|
+
code: "custom",
|
|
276
|
+
message: "foo",
|
|
277
|
+
fatal: true,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
})
|
|
281
|
+
.superRefine((val, ctx) => {
|
|
282
|
+
if (val !== " ") {
|
|
283
|
+
ctx.addIssue({
|
|
284
|
+
input: val,
|
|
285
|
+
code: "custom",
|
|
286
|
+
message: "bar",
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const result = schema.safeParse("");
|
|
292
|
+
expect(result.success).toEqual(false);
|
|
293
|
+
if (!result.success) {
|
|
294
|
+
expect(result.error.issues.length).toEqual(1);
|
|
295
|
+
expect(result.error.issues[0].message).toEqual("foo");
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe("chained refinements", () => {
|
|
301
|
+
test("should collect all validation errors when appropriate", () => {
|
|
302
|
+
const objectSchema = z
|
|
303
|
+
.object({
|
|
304
|
+
length: z.number(),
|
|
305
|
+
size: z.number(),
|
|
306
|
+
})
|
|
307
|
+
.refine(({ length }) => length > 5, {
|
|
308
|
+
path: ["length"],
|
|
309
|
+
message: "length greater than 5",
|
|
310
|
+
})
|
|
311
|
+
.refine(({ size }) => size > 7, {
|
|
312
|
+
path: ["size"],
|
|
313
|
+
message: "size greater than 7",
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Should fail with one error
|
|
317
|
+
const r1 = objectSchema.safeParse({
|
|
318
|
+
length: 4,
|
|
319
|
+
size: 9,
|
|
320
|
+
});
|
|
321
|
+
expect(r1.success).toEqual(false);
|
|
322
|
+
if (!r1.success) {
|
|
323
|
+
expect(r1.error.issues.length).toEqual(1);
|
|
324
|
+
expect(r1.error.issues[0].path).toEqual(["length"]);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Should fail with two errors
|
|
328
|
+
const r2 = objectSchema.safeParse({
|
|
329
|
+
length: 4,
|
|
330
|
+
size: 3,
|
|
331
|
+
});
|
|
332
|
+
expect(r2.success).toEqual(false);
|
|
333
|
+
if (!r2.success) {
|
|
334
|
+
expect(r2.error.issues.length).toEqual(2);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Should pass with valid input
|
|
338
|
+
const validData = {
|
|
339
|
+
length: 6,
|
|
340
|
+
size: 8,
|
|
341
|
+
};
|
|
342
|
+
expect(objectSchema.parse(validData)).toEqual(validData);
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Commented tests can be uncommented once type-checking issues are resolved
|
|
347
|
+
/*
|
|
348
|
+
describe("type refinement", () => {
|
|
349
|
+
test("refinement type guard", () => {
|
|
350
|
+
const validationSchema = z.object({
|
|
351
|
+
a: z.string().refine((s): s is "a" => s === "a"),
|
|
352
|
+
});
|
|
353
|
+
type Input = z.input<typeof validationSchema>;
|
|
354
|
+
type Schema = z.infer<typeof validationSchema>;
|
|
355
|
+
|
|
356
|
+
expectTypeOf<Input["a"]>().not.toEqualTypeOf<"a">();
|
|
357
|
+
expectTypeOf<Input["a"]>().toEqualTypeOf<string>();
|
|
358
|
+
|
|
359
|
+
expectTypeOf<Schema["a"]>().toEqualTypeOf<"a">();
|
|
360
|
+
expectTypeOf<Schema["a"]>().not.toEqualTypeOf<string>();
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("superRefine - type narrowing", () => {
|
|
364
|
+
type NarrowType = { type: string; age: number };
|
|
365
|
+
const schema = z
|
|
366
|
+
.object({
|
|
367
|
+
type: z.string(),
|
|
368
|
+
age: z.number(),
|
|
369
|
+
})
|
|
370
|
+
.nullable()
|
|
371
|
+
.superRefine((arg, ctx): arg is NarrowType => {
|
|
372
|
+
if (!arg) {
|
|
373
|
+
// still need to make a call to ctx.addIssue
|
|
374
|
+
ctx.addIssue({
|
|
375
|
+
input: arg,
|
|
376
|
+
code: "custom",
|
|
377
|
+
message: "cannot be null",
|
|
378
|
+
fatal: true,
|
|
379
|
+
});
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
return true;
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<NarrowType>();
|
|
386
|
+
|
|
387
|
+
expect(schema.safeParse({ type: "test", age: 0 }).success).toEqual(true);
|
|
388
|
+
expect(schema.safeParse(null).success).toEqual(false);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test("chained mixed refining types", () => {
|
|
392
|
+
type firstRefinement = { first: string; second: number; third: true };
|
|
393
|
+
type secondRefinement = { first: "bob"; second: number; third: true };
|
|
394
|
+
type thirdRefinement = { first: "bob"; second: 33; third: true };
|
|
395
|
+
const schema = z
|
|
396
|
+
.object({
|
|
397
|
+
first: z.string(),
|
|
398
|
+
second: z.number(),
|
|
399
|
+
third: z.boolean(),
|
|
400
|
+
})
|
|
401
|
+
.nullable()
|
|
402
|
+
.refine((arg): arg is firstRefinement => !!arg?.third)
|
|
403
|
+
.superRefine((arg, ctx): arg is secondRefinement => {
|
|
404
|
+
expectTypeOf<typeof arg>().toEqualTypeOf<firstRefinement>();
|
|
405
|
+
if (arg.first !== "bob") {
|
|
406
|
+
ctx.addIssue({
|
|
407
|
+
input: arg,
|
|
408
|
+
code: "custom",
|
|
409
|
+
message: "`first` property must be `bob`",
|
|
410
|
+
});
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
return true;
|
|
414
|
+
})
|
|
415
|
+
.refine((arg): arg is thirdRefinement => {
|
|
416
|
+
expectTypeOf<typeof arg>().toEqualTypeOf<secondRefinement>();
|
|
417
|
+
return arg.second === 33;
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<thirdRefinement>();
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
*/
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { expect, expectTypeOf, test } from "vitest";
|
|
2
|
+
import * as z from "zod/v4";
|
|
3
|
+
|
|
4
|
+
test("globalRegistry", () => {
|
|
5
|
+
const reg = z.registry();
|
|
6
|
+
|
|
7
|
+
const a = z.string();
|
|
8
|
+
reg.add(a);
|
|
9
|
+
expect(reg.has(a)).toEqual(true);
|
|
10
|
+
|
|
11
|
+
reg.remove(a);
|
|
12
|
+
expect(reg.has(a)).toEqual(false);
|
|
13
|
+
|
|
14
|
+
a.register(z.globalRegistry, { field: "sup" });
|
|
15
|
+
expect(z.globalRegistry.has(a)).toEqual(true);
|
|
16
|
+
expect(z.globalRegistry.get(a)).toEqual({ field: "sup" });
|
|
17
|
+
|
|
18
|
+
z.globalRegistry.remove(a);
|
|
19
|
+
expect(z.globalRegistry.has(a)).toEqual(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("z.registry", () => {
|
|
23
|
+
const fieldRegistry = z.registry<{ name: string; description: string }>();
|
|
24
|
+
|
|
25
|
+
const a = z.string();
|
|
26
|
+
fieldRegistry.add(a, { name: "hello", description: "world" });
|
|
27
|
+
const a_meta = fieldRegistry.get(a);
|
|
28
|
+
expect(a_meta).toEqual({ name: "hello", description: "world" });
|
|
29
|
+
|
|
30
|
+
fieldRegistry.remove(a);
|
|
31
|
+
expect(fieldRegistry.has(a)).toEqual(false);
|
|
32
|
+
expect(fieldRegistry.get(a)).toEqual(undefined);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("z.registry no metadata", () => {
|
|
36
|
+
const fieldRegistry = z.registry();
|
|
37
|
+
|
|
38
|
+
const a = z.string();
|
|
39
|
+
fieldRegistry.add(a);
|
|
40
|
+
fieldRegistry.add(z.number());
|
|
41
|
+
expect(fieldRegistry.get(a)).toEqual(undefined);
|
|
42
|
+
expect(fieldRegistry.has(a)).toEqual(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("z.registry with schema constraints", () => {
|
|
46
|
+
const fieldRegistry = z.registry<{ name: string; description: string }, z.ZodString>();
|
|
47
|
+
|
|
48
|
+
const a = z.string();
|
|
49
|
+
fieldRegistry.add(a, { name: "hello", description: "world" });
|
|
50
|
+
// @ts-expect-error
|
|
51
|
+
fieldRegistry.add(z.number(), { name: "test" });
|
|
52
|
+
// @ts-expect-error
|
|
53
|
+
z.number().register(fieldRegistry, { name: "test", description: "test" });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// test("z.namedRegistry", () => {
|
|
57
|
+
// const namedReg = z
|
|
58
|
+
// .namedRegistry<{ name: string; description: string }>()
|
|
59
|
+
// .add(z.string(), { name: "hello", description: "world" })
|
|
60
|
+
// .add(z.number(), { name: "number", description: "number" });
|
|
61
|
+
|
|
62
|
+
// expect(namedReg.get("hello")).toEqual({
|
|
63
|
+
// name: "hello",
|
|
64
|
+
// description: "world",
|
|
65
|
+
// });
|
|
66
|
+
// expect(namedReg.has("hello")).toEqual(true);
|
|
67
|
+
// expect(namedReg.get("number")).toEqual({
|
|
68
|
+
// name: "number",
|
|
69
|
+
// description: "number",
|
|
70
|
+
// });
|
|
71
|
+
|
|
72
|
+
// // @ts-expect-error
|
|
73
|
+
// namedReg.get("world");
|
|
74
|
+
// // @ts-expect-error
|
|
75
|
+
// expect(namedReg.get("world")).toEqual(undefined);
|
|
76
|
+
|
|
77
|
+
// const hello = namedReg.get("hello");
|
|
78
|
+
// expect(hello).toEqual({ name: "hello", description: "world" });
|
|
79
|
+
// expectTypeOf<typeof hello>().toEqualTypeOf<{
|
|
80
|
+
// name: "hello";
|
|
81
|
+
// description: "world";
|
|
82
|
+
// }>();
|
|
83
|
+
// expectTypeOf<typeof namedReg.items>().toEqualTypeOf<{
|
|
84
|
+
// hello: { name: "hello"; description: "world" };
|
|
85
|
+
// number: { name: "number"; description: "number" };
|
|
86
|
+
// }>();
|
|
87
|
+
// });
|
|
88
|
+
|
|
89
|
+
test("output type in registry meta", () => {
|
|
90
|
+
const reg = z.registry<{ out: z.$output }>();
|
|
91
|
+
const a = z.string();
|
|
92
|
+
reg.add(a, { out: "asdf" });
|
|
93
|
+
// @ts-expect-error
|
|
94
|
+
reg.add(a, 1234);
|
|
95
|
+
expectTypeOf(reg.get(a)).toEqualTypeOf<{ out: string } | undefined>();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("output type in registry meta - objects and arrays", () => {
|
|
99
|
+
const reg = z.registry<{ name: string; examples: z.$output[] }>();
|
|
100
|
+
const a = z.string();
|
|
101
|
+
reg.add(a, { name: "hello", examples: ["world"] });
|
|
102
|
+
|
|
103
|
+
// @ts-expect-error
|
|
104
|
+
reg.add(a, { name: "hello", examples: "world" });
|
|
105
|
+
expectTypeOf(reg.get(a)).toEqualTypeOf<{ name: string; examples: string[] } | undefined>();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("input type in registry meta", () => {
|
|
109
|
+
const reg = z.registry<{ in: z.$input }>();
|
|
110
|
+
const a = z.pipe(z.number(), z.transform(String));
|
|
111
|
+
reg.add(a, { in: 1234 });
|
|
112
|
+
// @ts-expect-error
|
|
113
|
+
reg.add(a, "1234");
|
|
114
|
+
expectTypeOf(reg.get(a)).toEqualTypeOf<{ in: number } | undefined>();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("input type in registry meta - objects and arrays", () => {
|
|
118
|
+
const reg = z.registry<{ name: string; examples: z.$input[] }>();
|
|
119
|
+
const a = z.pipe(z.number(), z.transform(String));
|
|
120
|
+
reg.add(a, { name: "hello", examples: [1234] });
|
|
121
|
+
|
|
122
|
+
// @ts-expect-error
|
|
123
|
+
reg.add(a, { name: "hello", examples: "world" });
|
|
124
|
+
expectTypeOf(reg.get(a)).toEqualTypeOf<{ name: string; examples: number[] } | undefined>();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test(".meta method", () => {
|
|
128
|
+
const a1 = z.string();
|
|
129
|
+
const a2 = a1.meta({ name: "hello" });
|
|
130
|
+
|
|
131
|
+
expect(a1.meta()).toEqual(undefined);
|
|
132
|
+
expect(a2.meta()).toEqual({ name: "hello" });
|
|
133
|
+
expect(a1 === a2).toEqual(false);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test(".meta metadata does not bubble up", () => {
|
|
137
|
+
const a1 = z.string().meta({ name: "hello" });
|
|
138
|
+
const a2 = a1.optional();
|
|
139
|
+
|
|
140
|
+
expect(a1.meta()).toEqual({ name: "hello" });
|
|
141
|
+
expect(a2.meta()).toEqual(undefined);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test(".describe", () => {
|
|
145
|
+
const a1 = z.string();
|
|
146
|
+
const a2 = a1.describe("Hello");
|
|
147
|
+
|
|
148
|
+
expect(a1.description).toEqual(undefined);
|
|
149
|
+
expect(a2.description).toEqual("Hello");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("inherit across clone", () => {
|
|
153
|
+
const A = z.string().meta({ a: true });
|
|
154
|
+
expect(A.meta()).toEqual({ a: true });
|
|
155
|
+
const B = A.meta({ b: true });
|
|
156
|
+
expect(B.meta()).toEqual({ a: true, b: true });
|
|
157
|
+
const C = B.describe("hello");
|
|
158
|
+
expect(C.meta()).toEqual({ a: true, b: true, description: "hello" });
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("loose examples", () => {
|
|
162
|
+
z.string().register(z.globalRegistry, {
|
|
163
|
+
examples: ["example"],
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("function meta witout replacement", () => {
|
|
168
|
+
const myReg = z.registry<{
|
|
169
|
+
defaulter: (arg: string, test: boolean) => number;
|
|
170
|
+
}>();
|
|
171
|
+
|
|
172
|
+
const mySchema = z.date();
|
|
173
|
+
myReg.add(mySchema, {
|
|
174
|
+
defaulter: (arg, _test) => {
|
|
175
|
+
return arg.length;
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
expect(myReg.get(mySchema)!.defaulter("hello", true)).toEqual(5);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("function meta with replacement", () => {
|
|
183
|
+
const myReg = z.registry<{
|
|
184
|
+
defaulter: (arg: z.$input, test: boolean) => z.$output;
|
|
185
|
+
}>();
|
|
186
|
+
|
|
187
|
+
const mySchema = z.string().transform((val) => val.length);
|
|
188
|
+
myReg.add(mySchema, {
|
|
189
|
+
defaulter: (arg, _test) => {
|
|
190
|
+
return arg.length;
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
expect(myReg.get(mySchema)!.defaulter("hello", true)).toEqual(5);
|
|
195
|
+
});
|