From harness-claude
Handles Zod validation failures with safeParse, ZodError.format, flatten, and custom error maps for API responses, form feedback, logging, and consistent error shapes.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Handle Zod validation failures with safeParse, ZodError, error.format, error.flatten, and custom error maps
Defines type-safe Zod schemas for forms, APIs, env vars; covers parsing, refinements, transformations, type inference, and integrations with React Hook Form, Next.js, tRPC.
Uses Zod schemas for runtime validation of APIs, forms, env vars, and external data while inferring TypeScript types as single source of truth.
Share bugs, ideas, or general feedback.
Handle Zod validation failures with safeParse, ZodError, error.format, error.flatten, and custom error maps
.safeParse() instead of .parse() whenever the caller needs to handle errors — it never throws:import { z } from 'zod';
const UserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().min(0),
});
const result = UserSchema.safeParse(rawInput);
if (!result.success) {
// result.error is a ZodError
console.error(result.error.issues);
return { errors: result.error.format() };
}
// result.data is typed
const user = result.data;
error.format() to produce a nested error object mirroring the schema shape:const result = UserSchema.safeParse({ name: '', email: 'not-an-email', age: -1 });
if (!result.success) {
const formatted = result.error.format();
/*
{
_errors: [],
name: { _errors: ['String must contain at least 1 character(s)'] },
email: { _errors: ['Invalid email'] },
age: { _errors: ['Number must be greater than or equal to 0'] }
}
*/
}
error.flatten() for a flat error object — simpler to consume in UI frameworks:const flattened = result.error.flatten();
/*
{
formErrors: [], // top-level (non-field) errors
fieldErrors: {
name: ['String must contain at least 1 character(s)'],
email: ['Invalid email'],
age: ['Number must be greater than or equal to 0']
}
}
*/
// Access field errors
const nameErrors = flattened.fieldErrors.name ?? [];
import { z, ZodIssueCode } from 'zod';
const result = UserSchema.safeParse(rawInput);
if (!result.success) {
for (const issue of result.error.issues) {
console.log(issue.path); // ['email']
console.log(issue.code); // 'invalid_string'
console.log(issue.message); // 'Invalid email'
}
}
z.setErrorMap() to override error messages globally (e.g., for i18n):import { z, ZodErrorMap, ZodIssueCode } from 'zod';
const customErrorMap: ZodErrorMap = (issue, ctx) => {
if (issue.code === ZodIssueCode.invalid_type) {
if (issue.expected === 'string') {
return { message: 'This field requires a text value' };
}
}
if (issue.code === ZodIssueCode.too_small && issue.type === 'string') {
return { message: `Must be at least ${issue.minimum} characters` };
}
return { message: ctx.defaultError };
};
z.setErrorMap(customErrorMap);
const result = UserSchema.safeParse(rawInput, {
errorMap: (issue, ctx) => {
if (issue.path[0] === 'email') {
return { message: 'Please enter a valid email address' };
}
return { message: ctx.defaultError };
},
});
function formatValidationError(error: z.ZodError): Record<string, string[]> {
return error.flatten().fieldErrors as Record<string, string[]>;
}
// In a Next.js API route or server action:
const result = CreateUserSchema.safeParse(await req.json());
if (!result.success) {
return Response.json(
{ success: false, errors: formatValidationError(result.error) },
{ status: 400 }
);
}
ZodError structure:
A ZodError is an Error subclass with an issues array. Each issue has:
| Field | Type | Description |
|---|---|---|
code | ZodIssueCode | The error category (e.g., invalid_type, too_small) |
path | (string | number)[] | Path to the failing field |
message | string | Human-readable error message |
Checking for specific error types:
import { ZodError, ZodIssueCode } from 'zod';
function isValidationError(err: unknown): err is ZodError {
return err instanceof ZodError;
}
function hasEmailError(err: ZodError): boolean {
return err.issues.some(
(issue) => issue.path.includes('email') && issue.code === ZodIssueCode.invalid_string
);
}
safeParseAsync:
For schemas with async refinements or transforms, use safeParseAsync():
const result = await UserSchema.safeParseAsync(rawInput);
Logging without sensitive data:
Before logging Zod errors, sanitize the input to avoid logging passwords or tokens:
if (!result.success) {
logger.warn('Validation failed', {
issues: result.error.issues.map((i) => ({ path: i.path, code: i.code, message: i.message })),
// Do NOT log the raw input
});
}
https://zod.dev/error-handling