Skip to content

Commit

Permalink
Enhance discriminative union errors
Browse files Browse the repository at this point in the history
  • Loading branch information
Doryski committed Oct 4, 2024
1 parent 3032e24 commit 9078140
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 32 deletions.
38 changes: 25 additions & 13 deletions src/ZodError.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TypeOf, ZodType } from ".";
import type { ParsePath, TypeOf, ZodType } from ".";
import { Primitive } from "./helpers/typeAliases";
import { util, ZodParsedType } from "./helpers/util";

Expand Down Expand Up @@ -37,7 +37,7 @@ export const ZodIssueCode = util.arrayToEnum([
export type ZodIssueCode = keyof typeof ZodIssueCode;

export type ZodIssueBase = {
path: (string | number)[];
path: ParsePath;
message?: string;
};

Expand Down Expand Up @@ -66,6 +66,7 @@ export interface ZodInvalidUnionIssue extends ZodIssueBase {
export interface ZodInvalidUnionDiscriminatorIssue extends ZodIssueBase {
code: typeof ZodIssueCode.invalid_union_discriminator;
options: Primitive[];
received: string;
}

export interface ZodInvalidEnumValueIssue extends ZodIssueBase {
Expand Down Expand Up @@ -239,23 +240,27 @@ export class ZodError<T = any> extends Error {
let i = 0;
while (i < issue.path.length) {
const el = issue.path[i];
const value =
typeof el === "object" && "discriminator" in el && "value" in el
? el.value
: el;
const terminal = i === issue.path.length - 1;

if (!terminal) {
curr[el] = curr[el] || { _errors: [] };
// if (typeof el === "string") {
// curr[el] = curr[el] || { _errors: [] };
// } else if (typeof el === "number") {
curr[value] = curr[value] || { _errors: [] };
// if (typeof value === "string") {
// curr[value] = curr[value] || { _errors: [] };
// } valuese if (typeof value === "number") {
// const errorArray: any = [];
// errorArray._errors = [];
// curr[el] = curr[el] || errorArray;
// curr[value] = curr[value] || errorArray;
// }
} else {
curr[el] = curr[el] || { _errors: [] };
curr[el]._errors.push(mapper(issue));
curr[value] = curr[value] || { _errors: [] };
curr[value]._errors.push(mapper(issue));
}

curr = curr[el];
curr = curr[value];
i++;
}
}
Expand Down Expand Up @@ -305,8 +310,15 @@ export class ZodError<T = any> extends Error {
const formErrors: U[] = [];
for (const sub of this.issues) {
if (sub.path.length > 0) {
fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || [];
fieldErrors[sub.path[0]].push(mapper(sub));
const pathItem = sub.path[0];
const value =
typeof pathItem === "object" &&
"discriminator" in pathItem &&
"value" in pathItem
? pathItem.value
: pathItem;
fieldErrors[value] = fieldErrors[value] || [];
fieldErrors[value].push(mapper(sub));
} else {
formErrors.push(mapper(sub));
}
Expand All @@ -324,7 +336,7 @@ type stripPath<T extends object> = T extends any
: never;

export type IssueData = stripPath<ZodIssueOptionalMessage> & {
path?: (string | number)[];
path?: ParsePath;
fatal?: boolean;
};

Expand Down
8 changes: 5 additions & 3 deletions src/__tests__/discriminated-unions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,10 @@ test("invalid discriminator value", () => {
{
code: z.ZodIssueCode.invalid_union_discriminator,
options: ["a", "b"],
message: "Invalid discriminator value. Expected 'a' | 'b'",
message:
"Invalid discriminator value. Expected 'a' | 'b', received 'x'",
path: ["type"],
received: "x",
},
]);
}
Expand All @@ -114,7 +116,7 @@ test("valid discriminator value, invalid data", () => {
code: z.ZodIssueCode.invalid_type,
expected: z.ZodParsedType.string,
message: "Required",
path: ["a"],
path: [{ discriminator: "type", value: "a" }, "a"],
received: z.ZodParsedType.undefined,
},
]);
Expand Down Expand Up @@ -189,7 +191,7 @@ test("async - invalid", async () => {
code: "invalid_type",
expected: "string",
received: "number",
path: ["a"],
path: [{ discriminator: "type", value: "a" }, "a"],
message: "Expected string, received number",
},
]);
Expand Down
11 changes: 7 additions & 4 deletions src/helpers/parseUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { ZodParsedType } from "./util";

export const makeIssue = (params: {
data: any;
path: (string | number)[];
path: ParsePath;
errorMaps: ZodErrorMap[];
issueData: IssueData;
}): ZodIssue => {
Expand Down Expand Up @@ -41,12 +41,15 @@ export const makeIssue = (params: {
};

export type ParseParams = {
path: (string | number)[];
path: ParsePath;
errorMap: ZodErrorMap;
async: boolean;
};

export type ParsePathComponent = string | number;
export type ParsePathComponent =
| string
| number
| { discriminator: string; value: string };
export type ParsePath = ParsePathComponent[];
export const EMPTY_PATH: ParsePath = [];

Expand All @@ -65,7 +68,7 @@ export interface ParseContext {

export type ParseInput = {
data: any;
path: (string | number)[];
path: ParsePath;
parent: ParseContext;
};

Expand Down
2 changes: 1 addition & 1 deletion src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const errorMap: ZodErrorMap = (issue, _ctx) => {
case ZodIssueCode.invalid_union_discriminator:
message = `Invalid discriminator value. Expected ${util.joinValues(
issue.options
)}`;
)}, received '${issue.received}'`;
break;
case ZodIssueCode.invalid_enum_value:
message = `Invalid enum value. Expected ${util.joinValues(
Expand Down
20 changes: 9 additions & 11 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import {

export interface RefinementCtx {
addIssue: (arg: IssueData) => void;
path: (string | number)[];
path: ParsePath;
}
export type ZodRawShape = { [k: string]: ZodTypeAny };
export type ZodTypeAny = ZodType<any, any, any>;
Expand Down Expand Up @@ -3138,22 +3138,20 @@ export class ZodDiscriminatedUnion<
code: ZodIssueCode.invalid_union_discriminator,
options: Array.from(this.optionsMap.keys()),
path: [discriminator],
received: discriminatorValue,
});
return INVALID;
}

const optionCtx = {
data: ctx.data,
path: [...ctx.path, { discriminator, value: discriminatorValue }],
parent: ctx,
};
if (ctx.common.async) {
return option._parseAsync({
data: ctx.data,
path: ctx.path,
parent: ctx,
}) as any;
return option._parseAsync(optionCtx) as any;
} else {
return option._parseSync({
data: ctx.data,
path: ctx.path,
parent: ctx,
}) as any;
return option._parseSync(optionCtx) as any;
}
}

Expand Down

0 comments on commit 9078140

Please sign in to comment.