Skip to content

Commit

Permalink
Fix ctx.addIssue in transform to work correctly with parseAsync (#1129)
Browse files Browse the repository at this point in the history
* Fix transform ctx.addIssue to work with parseAsync

* Add documentation about transform taking ctx
  • Loading branch information
bentefay committed May 12, 2022
1 parent b726328 commit ab8a371
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 5 deletions.
23 changes: 22 additions & 1 deletion README.md
Expand Up @@ -1605,7 +1605,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

0 comments on commit ab8a371

Please sign in to comment.