Skip to content

Commit 0969950

Browse files
Pingviinituutticolinhacks
andauthoredMay 21, 2023
adds ctx to preprocess (#2426)
* adds ctx to preprocess * Tweaks --------- Co-authored-by: Colin McDonnell <colinmcd94@gmail.com>
1 parent 37e9c55 commit 0969950

File tree

5 files changed

+184
-85
lines changed

5 files changed

+184
-85
lines changed
 

‎deno/lib/__tests__/transformer.test.ts

+67
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,73 @@ test("async preprocess", async () => {
212212
expect(value).toEqual(["asdf"]);
213213
});
214214

215+
test("preprocess ctx.addIssue with parse", () => {
216+
expect(() => {
217+
z.preprocess((data, ctx) => {
218+
ctx?.addIssue({
219+
code: "custom",
220+
message: `${data} is not one of our allowed strings`,
221+
});
222+
return data;
223+
}, z.string()).parse("asdf");
224+
}).toThrow(
225+
JSON.stringify(
226+
[
227+
{
228+
code: "custom",
229+
message: "asdf is not one of our allowed strings",
230+
path: [],
231+
},
232+
],
233+
null,
234+
2
235+
)
236+
);
237+
});
238+
239+
test("preprocess ctx.addIssue with parseAsync", async () => {
240+
const result = await z
241+
.preprocess((data, ctx) => {
242+
ctx?.addIssue({
243+
code: "custom",
244+
message: `${data} is not one of our allowed strings`,
245+
});
246+
return data;
247+
}, z.string())
248+
.safeParseAsync("asdf");
249+
250+
expect(JSON.parse(JSON.stringify(result))).toEqual({
251+
success: false,
252+
error: {
253+
issues: [
254+
{
255+
code: "custom",
256+
message: "asdf is not one of our allowed strings",
257+
path: [],
258+
},
259+
],
260+
name: "ZodError",
261+
},
262+
});
263+
});
264+
265+
test("z.NEVER in preprocess", async () => {
266+
const foo = z.preprocess((val, ctx) => {
267+
if (!val) {
268+
ctx?.addIssue({ code: z.ZodIssueCode.custom, message: "bad" });
269+
return z.NEVER;
270+
}
271+
return val;
272+
}, z.number());
273+
274+
type foo = z.infer<typeof foo>;
275+
util.assertEqual<foo, number>(true);
276+
const arg = foo.safeParse(undefined);
277+
if (!arg.success) {
278+
expect(arg.error.issues[0].message).toEqual("bad");
279+
}
280+
});
281+
215282
test("short circuit on dirty", () => {
216283
const schema = z
217284
.string()

‎deno/lib/types.ts

+25-19
Original file line numberDiff line numberDiff line change
@@ -4186,7 +4186,7 @@ export type TransformEffect<T> = {
41864186
};
41874187
export type PreprocessEffect<T> = {
41884188
type: "preprocess";
4189-
transform: (arg: T) => any;
4189+
transform: (arg: T, ctx: RefinementCtx) => any;
41904190
};
41914191
export type Effect<T> =
41924192
| RefinementEffect<T>
@@ -4220,8 +4220,30 @@ export class ZodEffects<
42204220

42214221
const effect = this._def.effect || null;
42224222

4223+
const checkCtx: RefinementCtx = {
4224+
addIssue: (arg: IssueData) => {
4225+
addIssueToContext(ctx, arg);
4226+
if (arg.fatal) {
4227+
status.abort();
4228+
} else {
4229+
status.dirty();
4230+
}
4231+
},
4232+
get path() {
4233+
return ctx.path;
4234+
},
4235+
};
4236+
4237+
checkCtx.addIssue = checkCtx.addIssue.bind(checkCtx);
4238+
42234239
if (effect.type === "preprocess") {
4224-
const processed = effect.transform(ctx.data);
4240+
const processed = effect.transform(ctx.data, checkCtx);
4241+
if (ctx.common.issues.length) {
4242+
return {
4243+
status: "dirty",
4244+
value: ctx.data,
4245+
};
4246+
}
42254247

42264248
if (ctx.common.async) {
42274249
return Promise.resolve(processed).then((processed) => {
@@ -4239,22 +4261,6 @@ export class ZodEffects<
42394261
});
42404262
}
42414263
}
4242-
4243-
const checkCtx: RefinementCtx = {
4244-
addIssue: (arg: IssueData) => {
4245-
addIssueToContext(ctx, arg);
4246-
if (arg.fatal) {
4247-
status.abort();
4248-
} else {
4249-
status.dirty();
4250-
}
4251-
},
4252-
get path() {
4253-
return ctx.path;
4254-
},
4255-
};
4256-
4257-
checkCtx.addIssue = checkCtx.addIssue.bind(checkCtx);
42584264
if (effect.type === "refinement") {
42594265
const executeRefinement = (
42604266
acc: unknown
@@ -4346,7 +4352,7 @@ export class ZodEffects<
43464352
};
43474353

43484354
static createWithPreprocess = <I extends ZodTypeAny>(
4349-
preprocess: (arg: unknown) => unknown,
4355+
preprocess: (arg: unknown, ctx: RefinementCtx) => unknown,
43504356
schema: I,
43514357
params?: RawCreateParams
43524358
): ZodEffects<I, I["_output"], unknown> => {

‎playground.ts

-47
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,2 @@
11
import { z } from "./src";
22
z;
3-
4-
console.log(
5-
z
6-
.string()
7-
.toUpperCase()
8-
.pipe(z.enum(["DE", "EN"]))
9-
.parse("de")
10-
);
11-
12-
function recursive<T extends z.ZodTypeAny>(
13-
callback: <G extends z.ZodTypeAny>(schema: G) => T
14-
): T {
15-
return "asdf" as any;
16-
}
17-
18-
z.string();
19-
20-
const cat = recursive((type) => {
21-
return z.object({
22-
name: z.string(),
23-
subcategories: type,
24-
});
25-
});
26-
type cat = z.infer<typeof cat>; //["subcategories"];
27-
declare let fido: cat;
28-
fido;
29-
fido.subcategories![0];
30-
31-
declare const __nominal__type: unique symbol;
32-
declare const __nominal__type2: unique symbol;
33-
34-
const arg = {
35-
a: "asdf",
36-
b: "asdf",
37-
c: "asdf",
38-
["$type"]: () => {},
39-
["@@type"]: () => {},
40-
["{type}"]: 1324,
41-
};
42-
43-
arg;
44-
45-
const kwarg = {
46-
[__nominal__type2]: "asdf",
47-
};
48-
49-
type aklmdf = typeof arg extends typeof kwarg ? true : false;

‎src/__tests__/transformer.test.ts

+67
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,73 @@ test("async preprocess", async () => {
211211
expect(value).toEqual(["asdf"]);
212212
});
213213

214+
test("preprocess ctx.addIssue with parse", () => {
215+
expect(() => {
216+
z.preprocess((data, ctx) => {
217+
ctx?.addIssue({
218+
code: "custom",
219+
message: `${data} is not one of our allowed strings`,
220+
});
221+
return data;
222+
}, z.string()).parse("asdf");
223+
}).toThrow(
224+
JSON.stringify(
225+
[
226+
{
227+
code: "custom",
228+
message: "asdf is not one of our allowed strings",
229+
path: [],
230+
},
231+
],
232+
null,
233+
2
234+
)
235+
);
236+
});
237+
238+
test("preprocess ctx.addIssue with parseAsync", async () => {
239+
const result = await z
240+
.preprocess((data, ctx) => {
241+
ctx?.addIssue({
242+
code: "custom",
243+
message: `${data} is not one of our allowed strings`,
244+
});
245+
return data;
246+
}, z.string())
247+
.safeParseAsync("asdf");
248+
249+
expect(JSON.parse(JSON.stringify(result))).toEqual({
250+
success: false,
251+
error: {
252+
issues: [
253+
{
254+
code: "custom",
255+
message: "asdf is not one of our allowed strings",
256+
path: [],
257+
},
258+
],
259+
name: "ZodError",
260+
},
261+
});
262+
});
263+
264+
test("z.NEVER in preprocess", async () => {
265+
const foo = z.preprocess((val, ctx) => {
266+
if (!val) {
267+
ctx?.addIssue({ code: z.ZodIssueCode.custom, message: "bad" });
268+
return z.NEVER;
269+
}
270+
return val;
271+
}, z.number());
272+
273+
type foo = z.infer<typeof foo>;
274+
util.assertEqual<foo, number>(true);
275+
const arg = foo.safeParse(undefined);
276+
if (!arg.success) {
277+
expect(arg.error.issues[0].message).toEqual("bad");
278+
}
279+
});
280+
214281
test("short circuit on dirty", () => {
215282
const schema = z
216283
.string()

‎src/types.ts

+25-19
Original file line numberDiff line numberDiff line change
@@ -4186,7 +4186,7 @@ export type TransformEffect<T> = {
41864186
};
41874187
export type PreprocessEffect<T> = {
41884188
type: "preprocess";
4189-
transform: (arg: T) => any;
4189+
transform: (arg: T, ctx: RefinementCtx) => any;
41904190
};
41914191
export type Effect<T> =
41924192
| RefinementEffect<T>
@@ -4220,8 +4220,30 @@ export class ZodEffects<
42204220

42214221
const effect = this._def.effect || null;
42224222

4223+
const checkCtx: RefinementCtx = {
4224+
addIssue: (arg: IssueData) => {
4225+
addIssueToContext(ctx, arg);
4226+
if (arg.fatal) {
4227+
status.abort();
4228+
} else {
4229+
status.dirty();
4230+
}
4231+
},
4232+
get path() {
4233+
return ctx.path;
4234+
},
4235+
};
4236+
4237+
checkCtx.addIssue = checkCtx.addIssue.bind(checkCtx);
4238+
42234239
if (effect.type === "preprocess") {
4224-
const processed = effect.transform(ctx.data);
4240+
const processed = effect.transform(ctx.data, checkCtx);
4241+
if (ctx.common.issues.length) {
4242+
return {
4243+
status: "dirty",
4244+
value: ctx.data,
4245+
};
4246+
}
42254247

42264248
if (ctx.common.async) {
42274249
return Promise.resolve(processed).then((processed) => {
@@ -4239,22 +4261,6 @@ export class ZodEffects<
42394261
});
42404262
}
42414263
}
4242-
4243-
const checkCtx: RefinementCtx = {
4244-
addIssue: (arg: IssueData) => {
4245-
addIssueToContext(ctx, arg);
4246-
if (arg.fatal) {
4247-
status.abort();
4248-
} else {
4249-
status.dirty();
4250-
}
4251-
},
4252-
get path() {
4253-
return ctx.path;
4254-
},
4255-
};
4256-
4257-
checkCtx.addIssue = checkCtx.addIssue.bind(checkCtx);
42584264
if (effect.type === "refinement") {
42594265
const executeRefinement = (
42604266
acc: unknown
@@ -4346,7 +4352,7 @@ export class ZodEffects<
43464352
};
43474353

43484354
static createWithPreprocess = <I extends ZodTypeAny>(
4349-
preprocess: (arg: unknown) => unknown,
4355+
preprocess: (arg: unknown, ctx: RefinementCtx) => unknown,
43504356
schema: I,
43514357
params?: RawCreateParams
43524358
): ZodEffects<I, I["_output"], unknown> => {

0 commit comments

Comments
 (0)
Please sign in to comment.