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

Fix ctx.addIssue in transform to work correctly with parseAsync #1129

Merged
merged 2 commits into from May 12, 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
23 changes: 22 additions & 1 deletion README.md
Expand Up @@ -1604,7 +1604,28 @@ const stringToNumber = z.string().transform((val) => myString.length);
stringToNumber.parse("string"); // => 6
```

> ⚠️ Transform functions must not throw. Make sure to use refinements before the transform to make sure the input can be parsed by the transform.
> ⚠️ Transform functions must not throw. Make sure to use refinements before the transform or addIssue within the transform to make sure the input can be parsed by the transform.

#### Validating during transform

Similar to `superRefine`, `transform` can optionally take a `ctx`. This allows you to simultaneously
validate and transform the value, which can be simpler than chaining `refine` and `validate`.
When calling `ctx.addIssue` make sure to still return a value of the correct type otherwise the inferred type will include `undefined`.

```ts
const Strings = z
.string()
.transform((val, ctx) => {
const parsed = parseInt(val);
if (isNaN(parsed)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Not a number",
});
}
return parsed;
});
```

#### Chaining order

Expand Down
34 changes: 33 additions & 1 deletion deno/lib/__tests__/transformer.test.ts
Expand Up @@ -11,7 +11,7 @@ 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", () => {
test("transform ctx.addIssue with parse", () => {
const strs = ["foo", "bar"];

expect(() => {
Expand Down Expand Up @@ -42,6 +42,38 @@ test("transform ctx.addIssue", () => {
);
});

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

const result = await 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;
})
.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("basic transformations", () => {
const r1 = z
.string()
Expand Down
2 changes: 1 addition & 1 deletion deno/lib/types.ts
Expand Up @@ -3328,7 +3328,7 @@ export class ZodEffects<
// return { status: "dirty", value: base.value };
// }
return Promise.resolve(effect.transform(base.value, checkCtx)).then(
OK
(result) => ({ status: status.value, value: result })
);
});
}
Expand Down
34 changes: 33 additions & 1 deletion src/__tests__/transformer.test.ts
Expand Up @@ -10,7 +10,7 @@ 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", () => {
test("transform ctx.addIssue with parse", () => {
const strs = ["foo", "bar"];

expect(() => {
Expand Down Expand Up @@ -41,6 +41,38 @@ test("transform ctx.addIssue", () => {
);
});

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

const result = await 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;
})
.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("basic transformations", () => {
const r1 = z
.string()
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Expand Up @@ -3328,7 +3328,7 @@ export class ZodEffects<
// return { status: "dirty", value: base.value };
// }
return Promise.resolve(effect.transform(base.value, checkCtx)).then(
OK
(result) => ({ status: status.value, value: result })
);
});
}
Expand Down