zod 4.1.0-canary.20250723T222937 → 4.1.0-canary.20250725T001018
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/v3/types.ts +3 -1
- package/src/v4/classic/errors.ts +9 -2
- package/src/v4/classic/schemas.ts +10 -7
- package/src/v4/classic/tests/error-utils.test.ts +43 -0
- package/src/v4/classic/tests/file.test.ts +5 -2
- package/src/v4/classic/tests/partial.test.ts +193 -0
- package/src/v4/classic/tests/pickomit.test.ts +5 -5
- package/src/v4/classic/tests/preprocess.test.ts +4 -15
- package/src/v4/classic/tests/record.test.ts +15 -1
- package/src/v4/classic/tests/recursive-types.test.ts +67 -0
- package/src/v4/classic/tests/string.test.ts +77 -0
- package/src/v4/classic/tests/to-json-schema.test.ts +1 -0
- package/src/v4/classic/tests/transform.test.ts +104 -0
- package/src/v4/classic/tests/union.test.ts +90 -3
- package/src/v4/core/checks.ts +2 -2
- package/src/v4/core/errors.ts +8 -15
- package/src/v4/core/registries.ts +3 -2
- package/src/v4/core/schemas.ts +92 -94
- package/src/v4/core/tests/extend.test.ts +18 -0
- package/src/v4/core/to-json-schema.ts +1 -0
- package/src/v4/core/util.ts +135 -98
- package/src/v4/core/versions.ts +1 -1
- package/src/v4/mini/schemas.ts +3 -1
- package/v3/types.cjs +2 -0
- package/v3/types.d.cts +4 -1
- package/v3/types.d.ts +4 -1
- package/v3/types.js +2 -0
- package/v4/classic/errors.cjs +9 -2
- package/v4/classic/errors.js +9 -2
- package/v4/classic/schemas.cjs +5 -3
- package/v4/classic/schemas.d.cts +2 -1
- package/v4/classic/schemas.d.ts +2 -1
- package/v4/classic/schemas.js +5 -3
- package/v4/core/checks.d.cts +2 -2
- package/v4/core/checks.d.ts +2 -2
- package/v4/core/errors.cjs +4 -9
- package/v4/core/errors.d.cts +4 -6
- package/v4/core/errors.d.ts +4 -6
- package/v4/core/errors.js +4 -9
- package/v4/core/registries.cjs +2 -1
- package/v4/core/registries.d.cts +1 -1
- package/v4/core/registries.d.ts +1 -1
- package/v4/core/registries.js +2 -1
- package/v4/core/schemas.cjs +50 -87
- package/v4/core/schemas.d.cts +8 -3
- package/v4/core/schemas.d.ts +8 -3
- package/v4/core/schemas.js +50 -87
- package/v4/core/to-json-schema.cjs +1 -0
- package/v4/core/to-json-schema.js +1 -0
- package/v4/core/util.cjs +123 -97
- package/v4/core/util.d.cts +2 -0
- package/v4/core/util.d.ts +2 -0
- package/v4/core/util.js +121 -97
- package/v4/core/versions.cjs +1 -1
- package/v4/core/versions.js +1 -1
- package/v4/mini/schemas.cjs +3 -1
- package/v4/mini/schemas.js +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zod",
|
|
3
|
-
"version": "4.1.0-canary.
|
|
3
|
+
"version": "4.1.0-canary.20250725T001018",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "Colin McDonnell <zod@colinhacks.com>",
|
|
6
6
|
"description": "TypeScript-first schema declaration and validation library with static type inference",
|
package/src/v3/types.ts
CHANGED
|
@@ -705,6 +705,7 @@ function isValidJWT(jwt: string, alg?: string): boolean {
|
|
|
705
705
|
.replace(/-/g, "+")
|
|
706
706
|
.replace(/_/g, "/")
|
|
707
707
|
.padEnd(header.length + ((4 - (header.length % 4)) % 4), "=");
|
|
708
|
+
// @ts-ignore
|
|
708
709
|
const decoded = JSON.parse(atob(base64));
|
|
709
710
|
if (typeof decoded !== "object" || decoded === null) return false;
|
|
710
711
|
if ("typ" in decoded && decoded?.typ !== "JWT") return false;
|
|
@@ -875,6 +876,7 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
|
|
|
875
876
|
}
|
|
876
877
|
} else if (check.kind === "url") {
|
|
877
878
|
try {
|
|
879
|
+
// @ts-ignore
|
|
878
880
|
new URL(input.data);
|
|
879
881
|
} catch {
|
|
880
882
|
ctx = this._getOrReturnCtx(input, ctx);
|
|
@@ -2454,7 +2456,7 @@ export class ZodObject<
|
|
|
2454
2456
|
Output = objectOutputType<T, Catchall, UnknownKeys>,
|
|
2455
2457
|
Input = objectInputType<T, Catchall, UnknownKeys>,
|
|
2456
2458
|
> extends ZodType<Output, ZodObjectDef<T, UnknownKeys, Catchall>, Input> {
|
|
2457
|
-
|
|
2459
|
+
_cached: { shape: T; keys: string[] } | null = null;
|
|
2458
2460
|
|
|
2459
2461
|
_getCached(): { shape: T; keys: string[] } {
|
|
2460
2462
|
if (this._cached !== null) return this._cached;
|
package/src/v4/classic/errors.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as core from "../core/index.js";
|
|
2
2
|
import { $ZodError } from "../core/index.js";
|
|
3
|
+
import * as util from "../core/util.js";
|
|
3
4
|
|
|
4
5
|
/** @deprecated Use `z.core.$ZodIssue` from `@zod/core` instead, especially if you are building a library on top of Zod. */
|
|
5
6
|
export type ZodIssue = core.$ZodIssue;
|
|
@@ -34,11 +35,17 @@ const initializer = (inst: ZodError, issues: core.$ZodIssue[]) => {
|
|
|
34
35
|
// enumerable: false,
|
|
35
36
|
},
|
|
36
37
|
addIssue: {
|
|
37
|
-
value: (issue: any) =>
|
|
38
|
+
value: (issue: any) => {
|
|
39
|
+
inst.issues.push(issue);
|
|
40
|
+
inst.message = JSON.stringify(inst.issues, util.jsonStringifyReplacer, 2);
|
|
41
|
+
},
|
|
38
42
|
// enumerable: false,
|
|
39
43
|
},
|
|
40
44
|
addIssues: {
|
|
41
|
-
value: (issues: any) =>
|
|
45
|
+
value: (issues: any) => {
|
|
46
|
+
inst.issues.push(...issues);
|
|
47
|
+
inst.message = JSON.stringify(inst.issues, util.jsonStringifyReplacer, 2);
|
|
48
|
+
},
|
|
42
49
|
// enumerable: false,
|
|
43
50
|
},
|
|
44
51
|
isEmpty: {
|
|
@@ -1068,9 +1068,7 @@ export interface ZodObject<
|
|
|
1068
1068
|
/** This is the default behavior. This method call is likely unnecessary. */
|
|
1069
1069
|
strip(): ZodObject<Shape, core.$strip>;
|
|
1070
1070
|
|
|
1071
|
-
extend<U extends core.$ZodLooseShape
|
|
1072
|
-
shape: U
|
|
1073
|
-
): ZodObject<util.Extend<Shape, U>, Config>;
|
|
1071
|
+
extend<U extends core.$ZodLooseShape>(shape: U): ZodObject<util.Extend<Shape, U>, Config>;
|
|
1074
1072
|
|
|
1075
1073
|
/**
|
|
1076
1074
|
* @deprecated Use [`A.extend(B.shape)`](https://zod.dev/api?id=extend) instead.
|
|
@@ -1130,7 +1128,6 @@ export const ZodObject: core.$constructor<ZodObject> = /*@__PURE__*/ core.$const
|
|
|
1130
1128
|
inst.keyof = () => _enum(Object.keys(inst._zod.def.shape)) as any;
|
|
1131
1129
|
inst.catchall = (catchall) => inst.clone({ ...inst._zod.def, catchall: catchall as any as core.$ZodType }) as any;
|
|
1132
1130
|
inst.passthrough = () => inst.clone({ ...inst._zod.def, catchall: unknown() });
|
|
1133
|
-
// inst.nonstrict = () => inst.clone({ ...inst._zod.def, catchall: api.unknown() });
|
|
1134
1131
|
inst.loose = () => inst.clone({ ...inst._zod.def, catchall: unknown() });
|
|
1135
1132
|
inst.strict = () => inst.clone({ ...inst._zod.def, catchall: never() });
|
|
1136
1133
|
inst.strip = () => inst.clone({ ...inst._zod.def, catchall: undefined });
|
|
@@ -1350,9 +1347,11 @@ export function partialRecord<Key extends core.$ZodRecordKey, Value extends core
|
|
|
1350
1347
|
valueType: Value,
|
|
1351
1348
|
params?: string | core.$ZodRecordParams
|
|
1352
1349
|
): ZodRecord<Key & core.$partial, Value> {
|
|
1350
|
+
const k = core.clone(keyType);
|
|
1351
|
+
k._zod.values = undefined;
|
|
1353
1352
|
return new ZodRecord({
|
|
1354
1353
|
type: "record",
|
|
1355
|
-
keyType:
|
|
1354
|
+
keyType: k,
|
|
1356
1355
|
valueType: valueType as any,
|
|
1357
1356
|
...util.normalizeParams(params),
|
|
1358
1357
|
}) as any;
|
|
@@ -1584,7 +1583,7 @@ export const ZodTransform: core.$constructor<ZodTransform> = /*@__PURE__*/ core.
|
|
|
1584
1583
|
_issue.code ??= "custom";
|
|
1585
1584
|
_issue.input ??= payload.value;
|
|
1586
1585
|
_issue.inst ??= inst;
|
|
1587
|
-
_issue.continue ??= true;
|
|
1586
|
+
// _issue.continue ??= true;
|
|
1588
1587
|
payload.issues.push(util.issue(_issue));
|
|
1589
1588
|
}
|
|
1590
1589
|
};
|
|
@@ -1838,12 +1837,16 @@ export function pipe(in_: core.SomeType, out: core.SomeType) {
|
|
|
1838
1837
|
// ZodReadonly
|
|
1839
1838
|
export interface ZodReadonly<T extends core.SomeType = core.$ZodType>
|
|
1840
1839
|
extends _ZodType<core.$ZodReadonlyInternals<T>>,
|
|
1841
|
-
core.$ZodReadonly<T> {
|
|
1840
|
+
core.$ZodReadonly<T> {
|
|
1841
|
+
unwrap(): T;
|
|
1842
|
+
}
|
|
1842
1843
|
export const ZodReadonly: core.$constructor<ZodReadonly> = /*@__PURE__*/ core.$constructor(
|
|
1843
1844
|
"ZodReadonly",
|
|
1844
1845
|
(inst, def) => {
|
|
1845
1846
|
core.$ZodReadonly.init(inst, def);
|
|
1846
1847
|
ZodType.init(inst, def);
|
|
1848
|
+
|
|
1849
|
+
inst.unwrap = () => inst._zod.def.innerType;
|
|
1847
1850
|
}
|
|
1848
1851
|
);
|
|
1849
1852
|
|
|
@@ -550,3 +550,46 @@ test("disc union treeify/format", () => {
|
|
|
550
550
|
}
|
|
551
551
|
`);
|
|
552
552
|
});
|
|
553
|
+
|
|
554
|
+
test("update message after adding issues", () => {
|
|
555
|
+
const e = new z.ZodError([]);
|
|
556
|
+
e.addIssue({
|
|
557
|
+
code: "custom",
|
|
558
|
+
message: "message",
|
|
559
|
+
input: "asdf",
|
|
560
|
+
path: [],
|
|
561
|
+
});
|
|
562
|
+
expect(e.message).toMatchInlineSnapshot(`
|
|
563
|
+
"[
|
|
564
|
+
{
|
|
565
|
+
"code": "custom",
|
|
566
|
+
"message": "message",
|
|
567
|
+
"input": "asdf",
|
|
568
|
+
"path": []
|
|
569
|
+
}
|
|
570
|
+
]"
|
|
571
|
+
`);
|
|
572
|
+
|
|
573
|
+
e.addIssue({
|
|
574
|
+
code: "custom",
|
|
575
|
+
message: "message",
|
|
576
|
+
input: "asdf",
|
|
577
|
+
path: [],
|
|
578
|
+
});
|
|
579
|
+
expect(e.message).toMatchInlineSnapshot(`
|
|
580
|
+
"[
|
|
581
|
+
{
|
|
582
|
+
"code": "custom",
|
|
583
|
+
"message": "message",
|
|
584
|
+
"input": "asdf",
|
|
585
|
+
"path": []
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
"code": "custom",
|
|
589
|
+
"message": "message",
|
|
590
|
+
"input": "asdf",
|
|
591
|
+
"path": []
|
|
592
|
+
}
|
|
593
|
+
]"
|
|
594
|
+
`);
|
|
595
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
// @ts-ignore
|
|
2
1
|
import { File as WebFile } from "@web-std/file";
|
|
3
2
|
|
|
4
|
-
import { afterEach, beforeEach, expect, test } from "vitest";
|
|
3
|
+
import { afterEach, beforeEach, expect, expectTypeOf, test } from "vitest";
|
|
5
4
|
|
|
6
5
|
import * as z from "zod/v4";
|
|
7
6
|
|
|
@@ -27,6 +26,10 @@ test("passing validations", () => {
|
|
|
27
26
|
expect(() => mimeCheck.parse(new File([""], "test.txt", { type: "text/csv" }))).toThrow();
|
|
28
27
|
});
|
|
29
28
|
|
|
29
|
+
test("types", () => {
|
|
30
|
+
expectTypeOf(z.file().parse(new File([], "test.txt"))).toEqualTypeOf(new File([], "test.txt"));
|
|
31
|
+
});
|
|
32
|
+
|
|
30
33
|
test("failing validations", () => {
|
|
31
34
|
expect(minCheck.safeParse(new File(["1234"], "test.txt"))).toMatchInlineSnapshot(`
|
|
32
35
|
{
|
|
@@ -145,3 +145,196 @@ test("partial with mask -- ignore falsy values", async () => {
|
|
|
145
145
|
masked.parse({ country: "US" });
|
|
146
146
|
await masked.parseAsync({ country: "US" });
|
|
147
147
|
});
|
|
148
|
+
|
|
149
|
+
test("catch/prefault/default", () => {
|
|
150
|
+
const mySchema = z.object({
|
|
151
|
+
a: z.string().catch("catch value").optional(),
|
|
152
|
+
b: z.string().default("default value").optional(),
|
|
153
|
+
c: z.string().prefault("prefault value").optional(),
|
|
154
|
+
d: z.string().catch("catch value"),
|
|
155
|
+
e: z.string().default("default value"),
|
|
156
|
+
f: z.string().prefault("prefault value"),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(mySchema.parse({})).toMatchInlineSnapshot(`
|
|
160
|
+
{
|
|
161
|
+
"b": "default value",
|
|
162
|
+
"c": "prefault value",
|
|
163
|
+
"d": "catch value",
|
|
164
|
+
"e": "default value",
|
|
165
|
+
"f": "prefault value",
|
|
166
|
+
}
|
|
167
|
+
`);
|
|
168
|
+
|
|
169
|
+
expect(mySchema.parse({}, { jitless: true })).toMatchInlineSnapshot(`
|
|
170
|
+
{
|
|
171
|
+
"b": "default value",
|
|
172
|
+
"c": "prefault value",
|
|
173
|
+
"d": "catch value",
|
|
174
|
+
"e": "default value",
|
|
175
|
+
"f": "prefault value",
|
|
176
|
+
}
|
|
177
|
+
`);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("handleOptionalObjectResult branches", () => {
|
|
181
|
+
const mySchema = z.object({
|
|
182
|
+
// Branch: input[key] === undefined, key not in input, caught error
|
|
183
|
+
caughtMissing: z.string().catch("caught").optional(),
|
|
184
|
+
// Branch: input[key] === undefined, key in input, caught error
|
|
185
|
+
caughtUndefined: z.string().catch("caught").optional(),
|
|
186
|
+
// Branch: input[key] === undefined, key not in input, validation issues
|
|
187
|
+
issueMissing: z.string().min(5).optional(),
|
|
188
|
+
// Branch: input[key] === undefined, key in input, validation issues
|
|
189
|
+
issueUndefined: z.string().min(5).optional(),
|
|
190
|
+
// Branch: input[key] === undefined, validation returns undefined
|
|
191
|
+
validUndefined: z.string().optional(),
|
|
192
|
+
// Branch: input[key] === undefined, non-undefined result (default/transform)
|
|
193
|
+
defaultValue: z.string().default("default").optional(),
|
|
194
|
+
// Branch: input[key] defined, caught error
|
|
195
|
+
caughtDefined: z.string().catch("caught").optional(),
|
|
196
|
+
// Branch: input[key] defined, validation issues
|
|
197
|
+
issueDefined: z.string().min(5).optional(),
|
|
198
|
+
// Branch: input[key] defined, validation returns undefined
|
|
199
|
+
validDefinedUndefined: z
|
|
200
|
+
.string()
|
|
201
|
+
.transform(() => undefined)
|
|
202
|
+
.optional(),
|
|
203
|
+
// Branch: input[key] defined, non-undefined value
|
|
204
|
+
validDefined: z.string().optional(),
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Test input[key] === undefined cases
|
|
208
|
+
const result1 = mySchema.parse(
|
|
209
|
+
{
|
|
210
|
+
// caughtMissing: not present (key not in input)
|
|
211
|
+
caughtUndefined: undefined, // key in input
|
|
212
|
+
// issueMissing: not present (key not in input)
|
|
213
|
+
issueUndefined: undefined, // key in input
|
|
214
|
+
validUndefined: undefined,
|
|
215
|
+
// defaultValue: not present, will get default
|
|
216
|
+
},
|
|
217
|
+
{ jitless: true }
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
expect(result1).toEqual({
|
|
221
|
+
caughtUndefined: undefined,
|
|
222
|
+
issueUndefined: undefined,
|
|
223
|
+
validUndefined: undefined,
|
|
224
|
+
defaultValue: "default",
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Test input[key] defined cases (successful)
|
|
228
|
+
const result2 = mySchema.parse(
|
|
229
|
+
{
|
|
230
|
+
caughtDefined: 123, // invalid type, should catch
|
|
231
|
+
validDefinedUndefined: "test", // transforms to undefined
|
|
232
|
+
validDefined: "valid", // valid value
|
|
233
|
+
},
|
|
234
|
+
{ jitless: true }
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
expect(result2).toEqual({
|
|
238
|
+
caughtDefined: "caught",
|
|
239
|
+
validDefinedUndefined: undefined,
|
|
240
|
+
validDefined: "valid",
|
|
241
|
+
defaultValue: "default",
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Test validation issues are properly reported (input[key] defined, validation fails)
|
|
245
|
+
expect(() =>
|
|
246
|
+
mySchema.parse(
|
|
247
|
+
{
|
|
248
|
+
issueDefined: "abc", // too short
|
|
249
|
+
},
|
|
250
|
+
{ jitless: true }
|
|
251
|
+
)
|
|
252
|
+
).toThrow();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("fastpass vs non-fastpass consistency", () => {
|
|
256
|
+
const mySchema = z.object({
|
|
257
|
+
caughtMissing: z.string().catch("caught").optional(),
|
|
258
|
+
caughtUndefined: z.string().catch("caught").optional(),
|
|
259
|
+
issueMissing: z.string().min(5).optional(),
|
|
260
|
+
issueUndefined: z.string().min(5).optional(),
|
|
261
|
+
validUndefined: z.string().optional(),
|
|
262
|
+
defaultValue: z.string().default("default").optional(),
|
|
263
|
+
caughtDefined: z.string().catch("caught").optional(),
|
|
264
|
+
validDefinedUndefined: z
|
|
265
|
+
.string()
|
|
266
|
+
.transform(() => undefined)
|
|
267
|
+
.optional(),
|
|
268
|
+
validDefined: z.string().optional(),
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const input = {
|
|
272
|
+
caughtUndefined: undefined,
|
|
273
|
+
issueUndefined: undefined,
|
|
274
|
+
validUndefined: undefined,
|
|
275
|
+
caughtDefined: 123,
|
|
276
|
+
validDefinedUndefined: "test",
|
|
277
|
+
validDefined: "valid",
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// Test both paths produce identical results
|
|
281
|
+
const jitlessResult = mySchema.parse(input, { jitless: true });
|
|
282
|
+
const fastpassResult = mySchema.parse(input);
|
|
283
|
+
|
|
284
|
+
expect(jitlessResult).toEqual(fastpassResult);
|
|
285
|
+
expect(jitlessResult).toEqual({
|
|
286
|
+
caughtUndefined: undefined,
|
|
287
|
+
issueUndefined: undefined,
|
|
288
|
+
validUndefined: undefined,
|
|
289
|
+
defaultValue: "default",
|
|
290
|
+
caughtDefined: "caught",
|
|
291
|
+
validDefinedUndefined: undefined,
|
|
292
|
+
validDefined: "valid",
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("optional with check", () => {
|
|
297
|
+
const baseSchema = z
|
|
298
|
+
.string()
|
|
299
|
+
.optional()
|
|
300
|
+
.check(({ value, ...ctx }) => {
|
|
301
|
+
ctx.issues.push({
|
|
302
|
+
code: "custom",
|
|
303
|
+
input: value,
|
|
304
|
+
message: "message",
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// this correctly fails
|
|
309
|
+
expect(baseSchema.safeParse(undefined)).toMatchInlineSnapshot(`
|
|
310
|
+
{
|
|
311
|
+
"error": [ZodError: [
|
|
312
|
+
{
|
|
313
|
+
"code": "custom",
|
|
314
|
+
"message": "message",
|
|
315
|
+
"path": []
|
|
316
|
+
}
|
|
317
|
+
]],
|
|
318
|
+
"success": false,
|
|
319
|
+
}
|
|
320
|
+
`);
|
|
321
|
+
|
|
322
|
+
const schemaObject = z.object({
|
|
323
|
+
date: baseSchema,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
expect(schemaObject.safeParse({ date: undefined })).toMatchInlineSnapshot(`
|
|
327
|
+
{
|
|
328
|
+
"error": [ZodError: [
|
|
329
|
+
{
|
|
330
|
+
"code": "custom",
|
|
331
|
+
"message": "message",
|
|
332
|
+
"path": [
|
|
333
|
+
"date"
|
|
334
|
+
]
|
|
335
|
+
}
|
|
336
|
+
]],
|
|
337
|
+
"success": false,
|
|
338
|
+
}
|
|
339
|
+
`);
|
|
340
|
+
});
|
|
@@ -114,14 +114,14 @@ test("pick/omit/required/partial - do not allow unknown keys", () => {
|
|
|
114
114
|
age: z.number(),
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
-
expect(() => schema.pick({ name: true, asdf: true })).toThrow();
|
|
117
|
+
expect(() => schema.pick({ name: true, asdf: true }).safeParse({})).toThrow();
|
|
118
118
|
|
|
119
119
|
// @ts-expect-error
|
|
120
|
-
expect(() => schema.pick({ $unknown: true })).toThrow();
|
|
120
|
+
expect(() => schema.pick({ $unknown: true }).safeParse({})).toThrow();
|
|
121
121
|
// @ts-expect-error
|
|
122
|
-
expect(() => schema.omit({ $unknown: true })).toThrow();
|
|
122
|
+
expect(() => schema.omit({ $unknown: true }).safeParse({})).toThrow();
|
|
123
123
|
// @ts-expect-error
|
|
124
|
-
expect(() => schema.required({ $unknown: true })).toThrow();
|
|
124
|
+
expect(() => schema.required({ $unknown: true }).safeParse({})).toThrow();
|
|
125
125
|
// @ts-expect-error
|
|
126
|
-
expect(() => schema.partial({ $unknown: true })).toThrow();
|
|
126
|
+
expect(() => schema.partial({ $unknown: true }).safeParse({})).toThrow();
|
|
127
127
|
});
|
|
@@ -73,17 +73,18 @@ test("preprocess ctx.addIssue with parse", () => {
|
|
|
73
73
|
`);
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
test("preprocess ctx.addIssue
|
|
76
|
+
test("preprocess ctx.addIssue fatal by default", () => {
|
|
77
77
|
const schema = z.preprocess((data, ctx) => {
|
|
78
78
|
ctx.addIssue({
|
|
79
79
|
code: "custom",
|
|
80
80
|
message: `custom error`,
|
|
81
81
|
});
|
|
82
|
+
|
|
82
83
|
return data;
|
|
83
84
|
}, z.string());
|
|
84
85
|
const result = schema.safeParse(1234);
|
|
85
86
|
|
|
86
|
-
expect(result.error!.issues).toHaveLength(
|
|
87
|
+
expect(result.error!.issues).toHaveLength(1);
|
|
87
88
|
expect(result).toMatchInlineSnapshot(`
|
|
88
89
|
{
|
|
89
90
|
"error": [ZodError: [
|
|
@@ -91,12 +92,6 @@ test("preprocess ctx.addIssue non-fatal by default", () => {
|
|
|
91
92
|
"code": "custom",
|
|
92
93
|
"message": "custom error",
|
|
93
94
|
"path": []
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
"expected": "string",
|
|
97
|
-
"code": "invalid_type",
|
|
98
|
-
"path": [],
|
|
99
|
-
"message": "Invalid input: expected string, received number"
|
|
100
95
|
}
|
|
101
96
|
]],
|
|
102
97
|
"success": false,
|
|
@@ -175,7 +170,7 @@ test("z.NEVER in preprocess", () => {
|
|
|
175
170
|
expectTypeOf<foo>().toEqualTypeOf<number>();
|
|
176
171
|
const result = foo.safeParse(undefined);
|
|
177
172
|
|
|
178
|
-
expect(result.error!.issues).toHaveLength(
|
|
173
|
+
expect(result.error!.issues).toHaveLength(1);
|
|
179
174
|
expect(result).toMatchInlineSnapshot(`
|
|
180
175
|
{
|
|
181
176
|
"error": [ZodError: [
|
|
@@ -183,12 +178,6 @@ test("z.NEVER in preprocess", () => {
|
|
|
183
178
|
"code": "custom",
|
|
184
179
|
"message": "bad",
|
|
185
180
|
"path": []
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
"expected": "number",
|
|
189
|
-
"code": "invalid_type",
|
|
190
|
-
"path": [],
|
|
191
|
-
"message": "Invalid input: expected number, received object"
|
|
192
181
|
}
|
|
193
182
|
]],
|
|
194
183
|
"success": false,
|
|
@@ -336,7 +336,21 @@ test("partial record", () => {
|
|
|
336
336
|
type schema = z.infer<typeof schema>;
|
|
337
337
|
expectTypeOf<schema>().toEqualTypeOf<Partial<Record<string, string>>>();
|
|
338
338
|
|
|
339
|
-
const Keys = z.enum(["id", "name", "email"])
|
|
339
|
+
const Keys = z.enum(["id", "name", "email"]); //.or(z.never());
|
|
340
340
|
const Person = z.partialRecord(Keys, z.string());
|
|
341
341
|
expectTypeOf<z.infer<typeof Person>>().toEqualTypeOf<Partial<Record<"id" | "name" | "email", string>>>();
|
|
342
|
+
|
|
343
|
+
Person.parse({
|
|
344
|
+
id: "123",
|
|
345
|
+
// name: "John",
|
|
346
|
+
// email: "john@example.com",
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
Person.parse({
|
|
350
|
+
// id: "123",
|
|
351
|
+
// name: "John",
|
|
352
|
+
email: "john@example.com",
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
expect(Person.def.keyType._zod.def.type).toEqual("enum");
|
|
342
356
|
});
|
|
@@ -260,6 +260,73 @@ test("mutual recursion with meta", () => {
|
|
|
260
260
|
expectTypeOf<B>().toEqualTypeOf<_B>();
|
|
261
261
|
});
|
|
262
262
|
|
|
263
|
+
test("object utilities with recursive types", () => {
|
|
264
|
+
const NodeBase = z.object({
|
|
265
|
+
id: z.string(),
|
|
266
|
+
name: z.string(),
|
|
267
|
+
get children() {
|
|
268
|
+
return z.array(Node).optional();
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Test extend
|
|
273
|
+
const NodeOne = NodeBase.extend({
|
|
274
|
+
name: z.literal("nodeOne"),
|
|
275
|
+
get children() {
|
|
276
|
+
return z.array(Node);
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const NodeTwo = NodeBase.extend({
|
|
281
|
+
name: z.literal("nodeTwo"),
|
|
282
|
+
get children() {
|
|
283
|
+
return z.array(Node);
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Test pick
|
|
288
|
+
const PickedNode = NodeBase.pick({ id: true, name: true });
|
|
289
|
+
|
|
290
|
+
// Test omit
|
|
291
|
+
const OmittedNode = NodeBase.omit({ children: true });
|
|
292
|
+
|
|
293
|
+
// Test merge
|
|
294
|
+
const ExtraProps = {
|
|
295
|
+
metadata: z.string(),
|
|
296
|
+
get parent() {
|
|
297
|
+
return Node.optional();
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
const MergedNode = NodeBase.extend(ExtraProps);
|
|
301
|
+
|
|
302
|
+
// Test partial
|
|
303
|
+
const PartialNode = NodeBase.partial();
|
|
304
|
+
const PartialMaskedNode = NodeBase.partial({ name: true });
|
|
305
|
+
|
|
306
|
+
// Test required (assuming NodeBase has optional fields)
|
|
307
|
+
const OptionalNodeBase = z.object({
|
|
308
|
+
id: z.string().optional(),
|
|
309
|
+
name: z.string().optional(),
|
|
310
|
+
get children() {
|
|
311
|
+
return z.array(Node).optional();
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
const RequiredNode = OptionalNodeBase.required();
|
|
315
|
+
const RequiredMaskedNode = OptionalNodeBase.required({ id: true });
|
|
316
|
+
|
|
317
|
+
const Node = z.union([
|
|
318
|
+
NodeOne,
|
|
319
|
+
NodeTwo,
|
|
320
|
+
PickedNode,
|
|
321
|
+
OmittedNode,
|
|
322
|
+
MergedNode,
|
|
323
|
+
PartialNode,
|
|
324
|
+
PartialMaskedNode,
|
|
325
|
+
RequiredNode,
|
|
326
|
+
RequiredMaskedNode,
|
|
327
|
+
]);
|
|
328
|
+
});
|
|
329
|
+
|
|
263
330
|
test("recursion compatibility", () => {
|
|
264
331
|
// array
|
|
265
332
|
const A = z.object({
|
|
@@ -318,6 +318,83 @@ test("url validations", () => {
|
|
|
318
318
|
expect(() => url.parse("https://")).toThrow();
|
|
319
319
|
});
|
|
320
320
|
|
|
321
|
+
test("url preserves original input", () => {
|
|
322
|
+
const url = z.string().url();
|
|
323
|
+
|
|
324
|
+
// Test the specific case from the user report
|
|
325
|
+
const input = "https://example.com?key=NUXOmHqWNVTapJkJJHw8BfD155AuqhH_qju_5fNmQ4ZHV7u8";
|
|
326
|
+
const output = url.parse(input);
|
|
327
|
+
expect(output).toBe(input); // Should preserve the original input exactly
|
|
328
|
+
|
|
329
|
+
// Test other cases where URL constructor would normalize
|
|
330
|
+
expect(url.parse("https://example.com?foo=bar")).toBe("https://example.com?foo=bar");
|
|
331
|
+
expect(url.parse("http://example.com?test=123")).toBe("http://example.com?test=123");
|
|
332
|
+
expect(url.parse("https://sub.example.com?param=value&other=data")).toBe(
|
|
333
|
+
"https://sub.example.com?param=value&other=data"
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
// Test cases with trailing slashes are preserved
|
|
337
|
+
expect(url.parse("https://example.com/")).toBe("https://example.com/");
|
|
338
|
+
expect(url.parse("https://example.com/path/")).toBe("https://example.com/path/");
|
|
339
|
+
|
|
340
|
+
// Test cases with paths and query parameters
|
|
341
|
+
expect(url.parse("https://example.com/path?query=param")).toBe("https://example.com/path?query=param");
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test("url trims whitespace", () => {
|
|
345
|
+
const url = z.string().url();
|
|
346
|
+
|
|
347
|
+
// Test trimming whitespace from URLs
|
|
348
|
+
expect(url.parse(" https://example.com ")).toBe("https://example.com");
|
|
349
|
+
expect(url.parse(" https://example.com/path?query=param ")).toBe("https://example.com/path?query=param");
|
|
350
|
+
expect(url.parse("\t\nhttps://example.com\t\n")).toBe("https://example.com");
|
|
351
|
+
expect(url.parse(" https://example.com?key=value ")).toBe("https://example.com?key=value");
|
|
352
|
+
|
|
353
|
+
// Test that URLs without extra whitespace are unchanged
|
|
354
|
+
expect(url.parse("https://example.com")).toBe("https://example.com");
|
|
355
|
+
expect(url.parse("https://example.com/path")).toBe("https://example.com/path");
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("url normalize flag", () => {
|
|
359
|
+
const normalizeUrl = z.url({ normalize: true });
|
|
360
|
+
const preserveUrl = z.url(); // normalize: false/undefined by default
|
|
361
|
+
|
|
362
|
+
// Test that normalize flag causes URL normalization
|
|
363
|
+
expect(normalizeUrl.parse("https://example.com?key=value")).toBe("https://example.com/?key=value");
|
|
364
|
+
expect(normalizeUrl.parse("http://example.com?test=123")).toBe("http://example.com/?test=123");
|
|
365
|
+
|
|
366
|
+
// Test with already normalized URLs
|
|
367
|
+
expect(normalizeUrl.parse("https://example.com/")).toBe("https://example.com/");
|
|
368
|
+
expect(normalizeUrl.parse("https://example.com/path?query=param")).toBe("https://example.com/path?query=param");
|
|
369
|
+
|
|
370
|
+
// Test complex URLs with normalization
|
|
371
|
+
expect(normalizeUrl.parse("https://example.com/../?key=value")).toBe("https://example.com/?key=value");
|
|
372
|
+
expect(normalizeUrl.parse("https://example.com/./path?key=value")).toBe("https://example.com/path?key=value");
|
|
373
|
+
|
|
374
|
+
// Compare with non-normalize behavior
|
|
375
|
+
expect(preserveUrl.parse("https://example.com?key=value")).toBe("https://example.com?key=value");
|
|
376
|
+
expect(preserveUrl.parse("http://example.com?test=123")).toBe("http://example.com?test=123");
|
|
377
|
+
|
|
378
|
+
// Test trimming with normalize
|
|
379
|
+
expect(normalizeUrl.parse(" https://example.com?key=value ")).toBe("https://example.com/?key=value");
|
|
380
|
+
expect(preserveUrl.parse(" https://example.com?key=value ")).toBe("https://example.com?key=value");
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test("url normalize with hostname and protocol constraints", () => {
|
|
384
|
+
const constrainedNormalizeUrl = z.url({
|
|
385
|
+
normalize: true,
|
|
386
|
+
protocol: /^https$/,
|
|
387
|
+
hostname: /^example\.com$/,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// Test that normalization works with constraints
|
|
391
|
+
expect(constrainedNormalizeUrl.parse("https://example.com?key=value")).toBe("https://example.com/?key=value");
|
|
392
|
+
|
|
393
|
+
// Test that constraints are still enforced
|
|
394
|
+
expect(() => constrainedNormalizeUrl.parse("http://example.com?key=value")).toThrow();
|
|
395
|
+
expect(() => constrainedNormalizeUrl.parse("https://other.com?key=value")).toThrow();
|
|
396
|
+
});
|
|
397
|
+
|
|
321
398
|
test("httpurl", () => {
|
|
322
399
|
const httpUrl = z.url({
|
|
323
400
|
protocol: /^https?$/,
|