Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow ctx.addIssue from transform #1056

Merged
merged 3 commits into from May 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -8,3 +8,4 @@ coverage
src/playground.ts
deno/lib/playground.ts
.eslintcache
workspace.code-workspace
31 changes: 31 additions & 0 deletions deno/lib/__tests__/transformer.test.ts
Expand Up @@ -11,6 +11,37 @@ const stringToNumber = z.string().transform((arg) => parseFloat(arg));
// .transform((n) => String(n));
const asyncNumberToString = z.number().transform(async (n) => String(n));

test("transform ctx.addIssue", () => {
const strs = ["foo", "bar"];

expect(() => {
z.string()
.transform((data, ctx) => {
const i = strs.indexOf(data);
if (i === -1) {
ctx.addIssue({
code: "custom",
message: `${data} is not one of our allowed strings`,
});
}
return data.length;
})
.parse("asdf");
}).toThrow(
JSON.stringify(
[
{
code: "custom",
message: "asdf is not one of our allowed strings",
path: [],
},
],
null,
2
)
);
});

test("basic transformations", () => {
const r1 = z
.string()
Expand Down
44 changes: 23 additions & 21 deletions deno/lib/types.ts
Expand Up @@ -422,7 +422,7 @@ export abstract class ZodType<
}

transform<NewOut>(
transform: (arg: Output) => NewOut | Promise<NewOut>
transform: (arg: Output, ctx: RefinementCtx) => NewOut | Promise<NewOut>
): ZodEffects<this, NewOut> {
return new ZodEffects({
schema: this,
Expand Down Expand Up @@ -3219,7 +3219,7 @@ export type RefinementEffect<T> = {
};
export type TransformEffect<T> = {
type: "transform";
transform: (arg: T) => any;
transform: (arg: T, ctx: RefinementCtx) => any;
};
export type PreprocessEffect<T> = {
type: "preprocess";
Expand Down Expand Up @@ -3271,23 +3271,22 @@ export class ZodEffects<
}
}

if (effect.type === "refinement") {
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);
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
// effect: RefinementEffect<any>
Expand Down Expand Up @@ -3343,13 +3342,14 @@ export class ZodEffects<
// }
if (!isValid(base)) return base;

const result = effect.transform(base.value);
const result = effect.transform(base.value, checkCtx);
if (result instanceof Promise) {
throw new Error(
`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`
);
}
return OK(result);

return { status: status.value, value: result };
} else {
return this._def.schema
._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx })
Expand All @@ -3359,7 +3359,9 @@ export class ZodEffects<
// if (base.status === "dirty") {
// return { status: "dirty", value: base.value };
// }
return Promise.resolve(effect.transform(base.value)).then(OK);
return Promise.resolve(effect.transform(base.value, checkCtx)).then(
OK
);
});
}
}
Expand Down
31 changes: 31 additions & 0 deletions src/__tests__/transformer.test.ts
Expand Up @@ -10,6 +10,37 @@ const stringToNumber = z.string().transform((arg) => parseFloat(arg));
// .transform((n) => String(n));
const asyncNumberToString = z.number().transform(async (n) => String(n));

test("transform ctx.addIssue", () => {
const strs = ["foo", "bar"];

expect(() => {
z.string()
.transform((data, ctx) => {
const i = strs.indexOf(data);
if (i === -1) {
ctx.addIssue({
code: "custom",
message: `${data} is not one of our allowed strings`,
});
}
return data.length;
})
.parse("asdf");
}).toThrow(
JSON.stringify(
[
{
code: "custom",
message: "asdf is not one of our allowed strings",
path: [],
},
],
null,
2
)
);
});

test("basic transformations", () => {
const r1 = z
.string()
Expand Down
44 changes: 23 additions & 21 deletions src/types.ts
Expand Up @@ -422,7 +422,7 @@ export abstract class ZodType<
}

transform<NewOut>(
transform: (arg: Output) => NewOut | Promise<NewOut>
transform: (arg: Output, ctx: RefinementCtx) => NewOut | Promise<NewOut>
): ZodEffects<this, NewOut> {
return new ZodEffects({
schema: this,
Expand Down Expand Up @@ -3219,7 +3219,7 @@ export type RefinementEffect<T> = {
};
export type TransformEffect<T> = {
type: "transform";
transform: (arg: T) => any;
transform: (arg: T, ctx: RefinementCtx) => any;
};
export type PreprocessEffect<T> = {
type: "preprocess";
Expand Down Expand Up @@ -3271,23 +3271,22 @@ export class ZodEffects<
}
}

if (effect.type === "refinement") {
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);
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
// effect: RefinementEffect<any>
Expand Down Expand Up @@ -3343,13 +3342,14 @@ export class ZodEffects<
// }
if (!isValid(base)) return base;

const result = effect.transform(base.value);
const result = effect.transform(base.value, checkCtx);
if (result instanceof Promise) {
throw new Error(
`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`
);
}
return OK(result);

return { status: status.value, value: result };
} else {
return this._def.schema
._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx })
Expand All @@ -3359,7 +3359,9 @@ export class ZodEffects<
// if (base.status === "dirty") {
// return { status: "dirty", value: base.value };
// }
return Promise.resolve(effect.transform(base.value)).then(OK);
return Promise.resolve(effect.transform(base.value, checkCtx)).then(
OK
);
});
}
}
Expand Down