Skip to content

Commit df73cb0

Browse files
committed
Make z.custom issues fatal
1 parent fa7bee4 commit df73cb0

File tree

12 files changed

+117
-46
lines changed

12 files changed

+117
-46
lines changed

packages/zod/src/v3/tests/preprocess.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,8 @@ test("z.NEVER in preprocess", () => {
136136
type foo = z.infer<typeof foo>;
137137
util.assertEqual<foo, number>(true);
138138
const arg = foo.safeParse(undefined);
139-
if (!arg.success) {
140-
expect(arg.error.issues[0].message).toEqual("bad");
141-
}
139+
expect(arg.error!.issues).toHaveLength(2);
140+
expect(arg.error!.issues[0].message).toEqual("bad");
142141
});
143142
test("preprocess as the second property of object", () => {
144143
const schema = z.object({

packages/zod/src/v4/classic/schemas.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1955,7 +1955,7 @@ export function refine<T>(
19551955
fn: (arg: NoInfer<T>) => util.MaybeAsync<unknown>,
19561956
_params: string | core.$ZodCustomParams = {}
19571957
): core.$ZodCheck<T> {
1958-
return core._custom(ZodCustom, fn, _params);
1958+
return core._refine(ZodCustom, fn, _params);
19591959
}
19601960

19611961
// superRefine

packages/zod/src/v4/classic/tests/custom.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,20 @@ test("instanceof", () => {
2121
// Argument of type 'ZodCustom<Uint8Array<ArrayBuffer>, unknown>' is not assignable to parameter of type '$ZodType<any, Uint8Array<ArrayBuffer>>'.
2222
z.string().transform(fn).pipe(z.instanceof(Uint8Array));
2323
});
24+
25+
test("non-continuable by default", () => {
26+
const A = z
27+
.custom<string>((val) => typeof val === "string")
28+
.transform((_) => {
29+
throw new Error("Invalid input");
30+
});
31+
expect(A.safeParse(123).error!).toMatchInlineSnapshot(`
32+
[ZodError: [
33+
{
34+
"code": "custom",
35+
"path": [],
36+
"message": "Invalid input"
37+
}
38+
]]
39+
`);
40+
});

packages/zod/src/v4/classic/tests/preprocess.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,3 +270,28 @@ test("preprocess validates with sibling errors", () => {
270270
}
271271
`);
272272
});
273+
274+
test("perform transform with non-fatal issues", () => {
275+
const A = z
276+
.string()
277+
.refine((_) => false)
278+
.min(4)
279+
.transform((val) => val.length)
280+
.pipe(z.number())
281+
.refine((_) => false);
282+
expect(A.safeParse("asdfasdf").error!.issues).toHaveLength(2);
283+
expect(A.safeParse("asdfasdf").error).toMatchInlineSnapshot(`
284+
[ZodError: [
285+
{
286+
"code": "custom",
287+
"path": [],
288+
"message": "Invalid input"
289+
},
290+
{
291+
"code": "custom",
292+
"path": [],
293+
"message": "Invalid input"
294+
}
295+
]]
296+
`);
297+
});

packages/zod/src/v4/classic/tests/to-json-schema.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,7 @@ describe("toJSONSchema", () => {
703703
{
704704
"$schema": "https://json-schema.org/draft/2020-12/schema",
705705
"const": "hello",
706+
"type": "string",
706707
}
707708
`);
708709

packages/zod/src/v4/core/api.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,22 +1434,39 @@ export function _custom<O = unknown, I = O>(
14341434
fn: (data: O) => unknown,
14351435
_params: string | $ZodCustomParams | undefined
14361436
): schemas.$ZodCustom<O, I> {
1437+
const norm = util.normalizeParams(_params);
1438+
norm.abort ??= true; // default to abort:false
14371439
const schema = new Class({
14381440
type: "custom",
14391441
check: "custom",
14401442
fn: fn as any,
1441-
...util.normalizeParams(_params),
1443+
...norm,
14421444
});
14431445

14441446
return schema as any;
14451447
}
14461448

1447-
export function _refine<T>(
1449+
// export function _refine<T>(
1450+
// Class: util.SchemaClass<schemas.$ZodCustom>,
1451+
// fn: (arg: NoInfer<T>) => util.MaybeAsync<unknown>,
1452+
// _params: string | $ZodCustomParams = {}
1453+
// ): checks.$ZodCheck<T> {
1454+
// return _custom(Class, fn, _params);
1455+
// }
1456+
// same as _custom but deafults to abort:false
1457+
export function _refine<O = unknown, I = O>(
14481458
Class: util.SchemaClass<schemas.$ZodCustom>,
1449-
fn: (arg: NoInfer<T>) => util.MaybeAsync<unknown>,
1450-
_params: string | $ZodCustomParams = {}
1451-
): checks.$ZodCheck<T> {
1452-
return _custom(Class, fn, _params);
1459+
fn: (data: O) => unknown,
1460+
_params: string | $ZodCustomParams | undefined
1461+
): schemas.$ZodCustom<O, I> {
1462+
const schema = new Class({
1463+
type: "custom",
1464+
check: "custom",
1465+
fn: fn as any,
1466+
...util.normalizeParams(_params),
1467+
});
1468+
1469+
return schema as any;
14531470
}
14541471

14551472
// export type $ZodCustomParams = CheckTypeParams<schemas.$ZodCustom, "fn">

packages/zod/src/v4/core/doc.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ export class Doc {
3939
const content = this?.content ?? [``];
4040
const lines = [...content.map((x) => ` ${x}`)];
4141
// console.log(lines.join("\n"));
42-
// console.dir("COMPILE", {depth: null});
4342
return new F(...args, lines.join("\n"));
4443
}
4544
}

packages/zod/src/v4/core/schemas.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2857,7 +2857,6 @@ export const $ZodTransform: core.$constructor<$ZodTransform> = /*@__PURE__*/ cor
28572857
$ZodType.init(inst, def);
28582858
inst._zod.parse = (payload, _ctx) => {
28592859
const _out = def.transform(payload.value, payload);
2860-
28612860
if (_ctx.async) {
28622861
const output = _out instanceof Promise ? _out : Promise.resolve(_out);
28632862
return output.then((output) => {
@@ -3373,7 +3372,6 @@ function handlePipeResult(left: ParsePayload, def: $ZodPipeDef, ctx: ParseContex
33733372
if (util.aborted(left)) {
33743373
return left;
33753374
}
3376-
33773375
return def.out._zod.run({ value: left.value, issues: left.issues }, ctx);
33783376
}
33793377

packages/zod/src/v4/core/to-json-schema.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,8 +432,13 @@ export class JSONSchemaGenerator {
432432
// do nothing (an undefined literal was stripped)
433433
} else if (vals.length === 1) {
434434
const val = vals[0];
435+
json.type = val === null ? "null" : typeof val;
435436
json.const = val;
436437
} else {
438+
if (vals.every((v) => typeof v === "number")) json.type = "number";
439+
if (vals.every((v) => typeof v === "string")) json.type = "string";
440+
if (vals.every((v) => typeof v === "boolean")) json.type = "string";
441+
if (vals.every((v) => v === null)) json.type = "null";
437442
json.enum = vals;
438443
}
439444
break;

packages/zod/src/v4/mini/schemas.ts

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,36 +1450,20 @@ export function check<O = unknown>(fn: core.CheckFn<O>, params?: string | core.$
14501450
}
14511451

14521452
// ZodCustom
1453-
function _custom<O = unknown, I = O>(
1454-
fn: (data: O) => unknown,
1455-
_params: string | core.$ZodCustomParams | undefined,
1456-
Class: util.Constructor<ZodMiniCustom, [core.$ZodCustomDef]>
1453+
// custom schema
1454+
export function custom<O = unknown, I = O>(
1455+
fn?: (data: O) => unknown,
1456+
_params?: string | core.$ZodCustomParams | undefined
14571457
): ZodMiniCustom<O, I> {
1458-
const params = util.normalizeParams(_params);
1459-
const schema = new Class({
1460-
type: "custom",
1461-
check: "custom",
1462-
fn: fn as any,
1463-
...params,
1464-
});
1465-
1466-
return schema as any;
1458+
return core._custom(ZodMiniCustom, fn ?? (() => true), _params) as any;
14671459
}
14681460

14691461
// refine
14701462
export function refine<T>(
14711463
fn: (arg: NoInfer<T>) => util.MaybeAsync<unknown>,
14721464
_params: string | core.$ZodCustomParams = {}
14731465
): core.$ZodCheck<T> {
1474-
return _custom(fn, _params, ZodMiniCustom);
1475-
}
1476-
1477-
// custom schema
1478-
export function custom<O = unknown, I = O>(
1479-
fn?: (data: O) => unknown,
1480-
_params?: string | core.$ZodCustomParams | undefined
1481-
): ZodMiniCustom<O, I> {
1482-
return _custom(fn ?? (() => true), _params, ZodMiniCustom);
1466+
return core._refine(ZodMiniCustom, fn, _params);
14831467
}
14841468

14851469
// instanceof

0 commit comments

Comments
 (0)