Skip to content

Commit

Permalink
feat: allow ctx.addIssue from transform (#1056)
Browse files Browse the repository at this point in the history
* feat: allow ctx.addIssue from transform

* chore: update transform test

* minor: lints
  • Loading branch information
morgs32 authored May 6, 2022
1 parent fa4eebb commit d54ba35
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 42 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,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 @@ -3187,7 +3187,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 @@ -3239,23 +3239,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 @@ -3311,13 +3310,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 @@ -3327,7 +3327,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,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 @@ -3187,7 +3187,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 @@ -3239,23 +3239,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 @@ -3311,13 +3310,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 @@ -3327,7 +3327,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

0 comments on commit d54ba35

Please sign in to comment.