Skip to content

Commit 0135c85

Browse files
author
Deepshekhar Das
authored
feat(v4): allow passing extra args to apply() (#6337)
Closes #5909.
1 parent ca246d2 commit 0135c85

5 files changed

Lines changed: 79 additions & 6 deletions

File tree

packages/docs/content/api.mdx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3340,3 +3340,16 @@ z.parse(schema, null); // => null
33403340
```
33413341
</Tab>
33423342
</Tabs>
3343+
3344+
Additional arguments are passed through to the function:
3345+
3346+
```ts
3347+
function withDefault<T extends z.ZodType>(schema: T, value: z.output<T>) {
3348+
return schema.nullish().transform((val) => val ?? value);
3349+
}
3350+
3351+
const schema = z.string().apply(withDefault, "anonymous");
3352+
3353+
schema.parse(undefined); // => "anonymous"
3354+
schema.parse("sandwich"); // => "sandwich"
3355+
```

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export interface ZodType<
159159
* ```
160160
*/
161161
isNullable(): boolean;
162-
apply<T>(fn: (schema: this) => T): T;
162+
apply<T, TArgs extends unknown[] = []>(fn: (schema: this, ...args: TArgs) => T, ...args: TArgs): T;
163163
}
164164

165165
export interface _ZodType<out Internals extends core.$ZodTypeInternals = core.$ZodTypeInternals>
@@ -292,8 +292,8 @@ function _zodTypeMethods(): _LazyMethodsOf<ZodType> {
292292
isNullable() {
293293
return this.safeParse(null).success;
294294
},
295-
apply(fn) {
296-
return fn(this);
295+
apply(fn, ...args) {
296+
return args.length === 0 ? fn(this) : fn(this, ...args);
297297
},
298298
};
299299
}

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,30 @@ test("The callback's return value becomes the apply's return value.", () => {
5757
expect(result).toBe(symbol);
5858
expectTypeOf<typeof result>().toEqualTypeOf<symbol>();
5959
});
60+
61+
test("apply forwards extra args to the callback", () => {
62+
const withDefault = <TSchema extends z.ZodType>(schema: TSchema, defaultValue: z.output<TSchema>) => {
63+
return schema.nullish().transform((x) => x ?? defaultValue);
64+
};
65+
66+
const schema = z.string().apply(withDefault, "default-id");
67+
68+
expect(schema.parse(undefined)).toBe("default-id");
69+
expect(schema.parse(null)).toBe("default-id");
70+
expect(schema.parse("value")).toBe("value");
71+
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<string>();
72+
});
73+
74+
test("apply type-checks the extra args", () => {
75+
const withMin = (schema: z.ZodString, n: number) => schema.min(n);
76+
77+
expect(z.string().apply(withMin, 3).parse("abcd")).toBe("abcd");
78+
expectTypeOf(z.number().apply<z.ZodNumber>((schema) => schema.min(0))).toEqualTypeOf<z.ZodNumber>();
79+
80+
// @ts-expect-error wrong arg type
81+
z.string().apply(withMin, "3");
82+
// @ts-expect-error missing arg
83+
z.string().apply(withMin);
84+
// @ts-expect-error extra arg for a single-parameter callback
85+
z.string().apply((schema: z.ZodString) => schema, 3);
86+
});

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export interface ZodMiniType<
3434
data: unknown,
3535
params?: core.ParseContext<core.$ZodIssue>
3636
): Promise<util.SafeParseResult<core.output<this>>>;
37-
apply<T>(fn: (schema: this) => T): T;
37+
apply<T, TArgs extends unknown[] = []>(fn: (schema: this, ...args: TArgs) => T, ...args: TArgs): T;
3838
}
3939

4040
interface _ZodMiniType<out Internals extends core.$ZodTypeInternals = core.$ZodTypeInternals>
@@ -95,8 +95,8 @@ function _zodMiniTypeMethods(): util.LazyMethodsOf<ZodMiniType> {
9595
reg.add(this, meta);
9696
return this;
9797
},
98-
apply(fn) {
99-
return fn(this);
98+
apply(fn, ...args) {
99+
return args.length === 0 ? fn(this) : fn(this, ...args);
100100
},
101101
};
102102
}

packages/zod/src/v4/mini/tests/apply.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,36 @@ test("The callback's return value becomes the apply's return value.", () => {
2222
expect(result).toBe(symbol);
2323
expectTypeOf<typeof result>().toEqualTypeOf<symbol>();
2424
});
25+
26+
test("apply forwards extra args to the callback", () => {
27+
const calls: unknown[] = [];
28+
const capture = (schema: z.ZodMiniString, ...args: unknown[]) => {
29+
calls.push(schema, ...args);
30+
return schema;
31+
};
32+
33+
const schema = z.string();
34+
const result = schema.apply(capture, "id", 42);
35+
36+
expect(calls[0]).toBe(schema);
37+
expect(calls[1]).toBe("id");
38+
expect(calls[2]).toBe(42);
39+
expect(z.parse(result, "value")).toBe("value");
40+
expectTypeOf<z.infer<typeof result>>().toEqualTypeOf<string>();
41+
});
42+
43+
test("apply type-checks the extra args", () => {
44+
const withMin = (schema: z.ZodMiniString, n: number) => schema.check(z.minLength(n));
45+
46+
expect(z.parse(z.string().apply(withMin, 3), "abcd")).toBe("abcd");
47+
expectTypeOf(
48+
z.number().apply<z.ZodMiniNumber>((schema) => schema.check(z.minimum(0)))
49+
).toEqualTypeOf<z.ZodMiniNumber>();
50+
51+
// @ts-expect-error wrong arg type
52+
z.string().apply(withMin, "3");
53+
// @ts-expect-error missing arg
54+
z.string().apply(withMin);
55+
// @ts-expect-error extra arg for a single-parameter callback
56+
z.string().apply((schema: z.ZodMiniString) => schema, 3);
57+
});

0 commit comments

Comments
 (0)