Skip to content

Commit 5e7456f

Browse files
committed
feat(utils): prettify zod errors in stringifyError utility
1 parent 68fe123 commit 5e7456f

File tree

4 files changed

+63
-2
lines changed

4 files changed

+63
-2
lines changed

packages/models/src/lib/implementation/validate.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export class SchemaValidationError extends Error {
2323
.filter(Boolean)
2424
.join(' ');
2525
super(`${summary}\n${formattedError}\n`);
26+
this.name = SchemaValidationError.name;
2627
}
2728
}
2829

packages/utils/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
"multi-progress-bars": "^5.0.3",
3838
"semver": "^7.6.0",
3939
"simple-git": "^3.20.0",
40-
"ora": "^9.0.0"
40+
"ora": "^9.0.0",
41+
"zod": "^4.0.5"
4142
},
4243
"files": [
4344
"src",

packages/utils/src/lib/errors.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1+
import { ZodError, z } from 'zod';
2+
13
export function stringifyError(error: unknown): string {
2-
// TODO: special handling for ZodError instances
4+
if (error instanceof ZodError) {
5+
const formattedError = z.prettifyError(error);
6+
if (formattedError.includes('\n')) {
7+
return `${error.name}:\n${formattedError}\n`;
8+
}
9+
return `${error.name}: ${formattedError}`;
10+
}
311
if (error instanceof Error) {
412
if (error.name === 'Error' || error.message.startsWith(error.name)) {
513
return error.message;

packages/utils/src/lib/errors.unit.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import ansis from 'ansis';
2+
import { z } from 'zod';
3+
import { SchemaValidationError } from '@code-pushup/models';
14
import { stringifyError } from './errors.js';
25

36
describe('stringifyError', () => {
@@ -22,4 +25,52 @@ describe('stringifyError', () => {
2225
'{"status":400,"statusText":"Bad Request"}',
2326
);
2427
});
28+
29+
it('should prettify ZodError instances spanning multiple lines', () => {
30+
const schema = z.object({
31+
name: z.string().min(1),
32+
address: z.string(),
33+
dateOfBirth: z.iso.date().optional(),
34+
});
35+
const { error } = schema.safeParse({ name: '', dateOfBirth: '' });
36+
37+
expect(stringifyError(error)).toBe(`ZodError:
38+
✖ Too small: expected string to have >=1 characters
39+
→ at name
40+
✖ Invalid input: expected string, received undefined
41+
→ at address
42+
✖ Invalid ISO date
43+
→ at dateOfBirth
44+
`);
45+
});
46+
47+
it('should prettify ZodError instances on one line if possible', () => {
48+
const schema = z.enum(['json', 'md']);
49+
const { error } = schema.safeParse('html');
50+
51+
expect(stringifyError(error)).toBe(
52+
'ZodError: ✖ Invalid option: expected one of "json"|"md"',
53+
);
54+
});
55+
56+
it('should use custom SchemaValidationError formatted messages', () => {
57+
const schema = z
58+
.object({
59+
name: z.string().min(1),
60+
address: z.string(),
61+
dateOfBirth: z.iso.date().optional(),
62+
})
63+
.meta({ title: 'User' });
64+
const { error } = schema.safeParse({ name: '', dateOfBirth: '' });
65+
66+
expect(stringifyError(new SchemaValidationError(error!, schema, {})))
67+
.toBe(`SchemaValidationError: Invalid ${ansis.bold('User')}
68+
✖ Too small: expected string to have >=1 characters
69+
→ at name
70+
✖ Invalid input: expected string, received undefined
71+
→ at address
72+
✖ Invalid ISO date
73+
→ at dateOfBirth
74+
`);
75+
});
2576
});

0 commit comments

Comments
 (0)