Skip to content

Commit

Permalink
adds ctx to preprocess (#2426)
Browse files Browse the repository at this point in the history
* adds ctx to preprocess

* Tweaks

---------

Co-authored-by: Colin McDonnell <colinmcd94@gmail.com>
  • Loading branch information
Pingviinituutti and colinhacks committed May 21, 2023
1 parent 37e9c55 commit 0969950
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 85 deletions.
67 changes: 67 additions & 0 deletions deno/lib/__tests__/transformer.test.ts
Expand Up @@ -212,6 +212,73 @@ test("async preprocess", async () => {
expect(value).toEqual(["asdf"]);
});

test("preprocess ctx.addIssue with parse", () => {
expect(() => {
z.preprocess((data, ctx) => {
ctx?.addIssue({
code: "custom",
message: `${data} is not one of our allowed strings`,
});
return data;
}, z.string()).parse("asdf");
}).toThrow(
JSON.stringify(
[
{
code: "custom",
message: "asdf is not one of our allowed strings",
path: [],
},
],
null,
2
)
);
});

test("preprocess ctx.addIssue with parseAsync", async () => {
const result = await z
.preprocess((data, ctx) => {
ctx?.addIssue({
code: "custom",
message: `${data} is not one of our allowed strings`,
});
return data;
}, z.string())
.safeParseAsync("asdf");

expect(JSON.parse(JSON.stringify(result))).toEqual({
success: false,
error: {
issues: [
{
code: "custom",
message: "asdf is not one of our allowed strings",
path: [],
},
],
name: "ZodError",
},
});
});

test("z.NEVER in preprocess", async () => {
const foo = z.preprocess((val, ctx) => {
if (!val) {
ctx?.addIssue({ code: z.ZodIssueCode.custom, message: "bad" });
return z.NEVER;
}
return val;
}, z.number());

type foo = z.infer<typeof foo>;
util.assertEqual<foo, number>(true);
const arg = foo.safeParse(undefined);
if (!arg.success) {
expect(arg.error.issues[0].message).toEqual("bad");
}
});

test("short circuit on dirty", () => {
const schema = z
.string()
Expand Down
44 changes: 25 additions & 19 deletions deno/lib/types.ts
Expand Up @@ -4186,7 +4186,7 @@ export type TransformEffect<T> = {
};
export type PreprocessEffect<T> = {
type: "preprocess";
transform: (arg: T) => any;
transform: (arg: T, ctx: RefinementCtx) => any;
};
export type Effect<T> =
| RefinementEffect<T>
Expand Down Expand Up @@ -4220,8 +4220,30 @@ export class ZodEffects<

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

const checkCtx: RefinementCtx = {
addIssue: (arg: IssueData) => {
addIssueToContext(ctx, arg);
if (arg.fatal) {
status.abort();
} else {
status.dirty();
}
},
get path() {
return ctx.path;
},
};

checkCtx.addIssue = checkCtx.addIssue.bind(checkCtx);

if (effect.type === "preprocess") {
const processed = effect.transform(ctx.data);
const processed = effect.transform(ctx.data, checkCtx);
if (ctx.common.issues.length) {
return {
status: "dirty",
value: ctx.data,
};
}

if (ctx.common.async) {
return Promise.resolve(processed).then((processed) => {
Expand All @@ -4239,22 +4261,6 @@ export class ZodEffects<
});
}
}

const checkCtx: RefinementCtx = {
addIssue: (arg: IssueData) => {
addIssueToContext(ctx, arg);
if (arg.fatal) {
status.abort();
} else {
status.dirty();
}
},
get path() {
return ctx.path;
},
};

checkCtx.addIssue = checkCtx.addIssue.bind(checkCtx);
if (effect.type === "refinement") {
const executeRefinement = (
acc: unknown
Expand Down Expand Up @@ -4346,7 +4352,7 @@ export class ZodEffects<
};

static createWithPreprocess = <I extends ZodTypeAny>(
preprocess: (arg: unknown) => unknown,
preprocess: (arg: unknown, ctx: RefinementCtx) => unknown,
schema: I,
params?: RawCreateParams
): ZodEffects<I, I["_output"], unknown> => {
Expand Down
47 changes: 0 additions & 47 deletions playground.ts
@@ -1,49 +1,2 @@
import { z } from "./src";
z;

console.log(
z
.string()
.toUpperCase()
.pipe(z.enum(["DE", "EN"]))
.parse("de")
);

function recursive<T extends z.ZodTypeAny>(
callback: <G extends z.ZodTypeAny>(schema: G) => T
): T {
return "asdf" as any;
}

z.string();

const cat = recursive((type) => {
return z.object({
name: z.string(),
subcategories: type,
});
});
type cat = z.infer<typeof cat>; //["subcategories"];
declare let fido: cat;
fido;
fido.subcategories![0];

declare const __nominal__type: unique symbol;
declare const __nominal__type2: unique symbol;

const arg = {
a: "asdf",
b: "asdf",
c: "asdf",
["$type"]: () => {},
["@@type"]: () => {},
["{type}"]: 1324,
};

arg;

const kwarg = {
[__nominal__type2]: "asdf",
};

type aklmdf = typeof arg extends typeof kwarg ? true : false;
67 changes: 67 additions & 0 deletions src/__tests__/transformer.test.ts
Expand Up @@ -211,6 +211,73 @@ test("async preprocess", async () => {
expect(value).toEqual(["asdf"]);
});

test("preprocess ctx.addIssue with parse", () => {
expect(() => {
z.preprocess((data, ctx) => {
ctx?.addIssue({
code: "custom",
message: `${data} is not one of our allowed strings`,
});
return data;
}, z.string()).parse("asdf");
}).toThrow(
JSON.stringify(
[
{
code: "custom",
message: "asdf is not one of our allowed strings",
path: [],
},
],
null,
2
)
);
});

test("preprocess ctx.addIssue with parseAsync", async () => {
const result = await z
.preprocess((data, ctx) => {
ctx?.addIssue({
code: "custom",
message: `${data} is not one of our allowed strings`,
});
return data;
}, z.string())
.safeParseAsync("asdf");

expect(JSON.parse(JSON.stringify(result))).toEqual({
success: false,
error: {
issues: [
{
code: "custom",
message: "asdf is not one of our allowed strings",
path: [],
},
],
name: "ZodError",
},
});
});

test("z.NEVER in preprocess", async () => {
const foo = z.preprocess((val, ctx) => {
if (!val) {
ctx?.addIssue({ code: z.ZodIssueCode.custom, message: "bad" });
return z.NEVER;
}
return val;
}, z.number());

type foo = z.infer<typeof foo>;
util.assertEqual<foo, number>(true);
const arg = foo.safeParse(undefined);
if (!arg.success) {
expect(arg.error.issues[0].message).toEqual("bad");
}
});

test("short circuit on dirty", () => {
const schema = z
.string()
Expand Down
44 changes: 25 additions & 19 deletions src/types.ts
Expand Up @@ -4186,7 +4186,7 @@ export type TransformEffect<T> = {
};
export type PreprocessEffect<T> = {
type: "preprocess";
transform: (arg: T) => any;
transform: (arg: T, ctx: RefinementCtx) => any;
};
export type Effect<T> =
| RefinementEffect<T>
Expand Down Expand Up @@ -4220,8 +4220,30 @@ export class ZodEffects<

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

const checkCtx: RefinementCtx = {
addIssue: (arg: IssueData) => {
addIssueToContext(ctx, arg);
if (arg.fatal) {
status.abort();
} else {
status.dirty();
}
},
get path() {
return ctx.path;
},
};

checkCtx.addIssue = checkCtx.addIssue.bind(checkCtx);

if (effect.type === "preprocess") {
const processed = effect.transform(ctx.data);
const processed = effect.transform(ctx.data, checkCtx);
if (ctx.common.issues.length) {
return {
status: "dirty",
value: ctx.data,
};
}

if (ctx.common.async) {
return Promise.resolve(processed).then((processed) => {
Expand All @@ -4239,22 +4261,6 @@ export class ZodEffects<
});
}
}

const checkCtx: RefinementCtx = {
addIssue: (arg: IssueData) => {
addIssueToContext(ctx, arg);
if (arg.fatal) {
status.abort();
} else {
status.dirty();
}
},
get path() {
return ctx.path;
},
};

checkCtx.addIssue = checkCtx.addIssue.bind(checkCtx);
if (effect.type === "refinement") {
const executeRefinement = (
acc: unknown
Expand Down Expand Up @@ -4346,7 +4352,7 @@ export class ZodEffects<
};

static createWithPreprocess = <I extends ZodTypeAny>(
preprocess: (arg: unknown) => unknown,
preprocess: (arg: unknown, ctx: RefinementCtx) => unknown,
schema: I,
params?: RawCreateParams
): ZodEffects<I, I["_output"], unknown> => {
Expand Down

0 comments on commit 0969950

Please sign in to comment.