Skip to content

Commit

Permalink
fix: union producing invalid message when using .or()
Browse files Browse the repository at this point in the history
* fix: union errors produce wrong message using

* fix: unexpected formating

* fix: add changeset

* fix: remove colon
  • Loading branch information
Profesor08 authored Feb 2, 2023
1 parent 7a486d0 commit 9c4c4ec
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/weak-dolls-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'zod-validation-error': patch
---

Make union errors more detailed
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"prettier/prettier": ["error", {}, { "usePrettierrc": true }],
"simple-import-sort/imports": "error",
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/ban-ts-comment": "off"
"@typescript-eslint/ban-ts-comment": "off",
"no-console": ["warn"]
},
"env": {
"node": true
Expand Down
73 changes: 73 additions & 0 deletions lib/ValidationError.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,79 @@ describe('fromZodError()', () => {
}
}
});

test('schema.parse() path param to be part of error message', () => {
const objSchema = zod.object({
status: zod.literal('success'),
});

try {
objSchema.parse(
{},
{
path: ['custom-path'],
}
);
} catch (err) {
if (err instanceof ZodError) {
const validationError = fromZodError(err);
expect(validationError).toBeInstanceOf(ValidationError);
expect(validationError.message).toMatchInlineSnapshot(
`"Validation error: Invalid literal value, expected "success" at "custom-path.status""`
);
}
}
});

test('handles zod.or() schema errors', () => {
const success = zod.object({
status: zod.literal('success'),
data: zod.object({
id: zod.string(),
}),
});

const error = zod.object({
status: zod.literal('error'),
});

const objSchema = success.or(error);

try {
objSchema.parse({});
} catch (err) {
if (err instanceof ZodError) {
const validationError = fromZodError(err);
expect(validationError).toBeInstanceOf(ValidationError);
expect(validationError.message).toMatchInlineSnapshot(
`"Validation error: Invalid literal value, expected "success" at "status"; Required at "data", or Invalid literal value, expected "error" at "status""`
);
}
}
});

test('handles zod.and() schema errors', () => {
const part1 = zod.object({
prop1: zod.literal('value1'),
});
const part2 = zod.object({
prop2: zod.literal('value2'),
});

const objSchema = part1.and(part2);

try {
objSchema.parse({});
} catch (err) {
if (err instanceof ZodError) {
const validationError = fromZodError(err);
expect(validationError).toBeInstanceOf(ValidationError);
expect(validationError.message).toMatchInlineSnapshot(
`"Validation error: Invalid literal value, expected "value1" at "prop1"; Invalid literal value, expected "value2" at "prop2""`
);
}
}
});
});

describe('isValidationError()', () => {
Expand Down
34 changes: 25 additions & 9 deletions lib/ValidationError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,42 @@ export class ValidationError extends Error {
}
}

function fromZodIssue(
issue: zod.ZodIssue,
issueSeparator: string,
unionSeparator: string
): string {
if (issue.code === 'invalid_union') {
return issue.unionErrors
.map((zodError) =>
zodError.issues
.map((issue) => fromZodIssue(issue, issueSeparator, unionSeparator))
.join(issueSeparator)
)
.join(unionSeparator);
}

if (issue.path.length > 0) {
return `${issue.message} at "${joinPath(issue.path)}"`;
}

return issue.message;
}

export function fromZodError(
zodError: zod.ZodError,
options: {
maxIssuesInMessage?: number;
issueSeparator?: string;
unionSeparator?: string;
prefixSeparator?: string;
prefix?: string;
} = {}
): ValidationError {
const {
maxIssuesInMessage = 99, // I've got 99 problems but the b$tch ain't one
issueSeparator = '; ',
unionSeparator = ', or ',
prefixSeparator = ': ',
prefix = 'Validation error',
} = options;
Expand All @@ -38,15 +62,7 @@ export function fromZodError(
// limit max number of issues printed in the reason section
.slice(0, maxIssuesInMessage)
// format error message
.map((issue) => {
const { message, path } = issue;

if (path.length > 0) {
return `${message} at "${joinPath(path)}"`;
}

return message;
})
.map((issue) => fromZodIssue(issue, issueSeparator, unionSeparator))
// concat as string
.join(issueSeparator);

Expand Down

0 comments on commit 9c4c4ec

Please sign in to comment.