All files / lib zv.ts

100% Statements 11/11
66.66% Branches 4/6
100% Functions 3/3
100% Lines 11/11

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57                                                    19x 119x 20x 20x                               20x 20x 22x 22x   22x 22x     20x    
import { zValidator } from "@hono/zod-validator";
import type { ValidationTargets } from "hono";
import type { ZodSchema, ZodIssue } from "zod";
 
/**
 * Wrapper around @hono/zod-validator that returns a stable, user-friendly
 * validation error envelope. The default zValidator sends the full Zod result
 * (including the stringified `issues` array) as the response — that array leaks
 * into the UI as raw JSON.
 *
 * Response shape on validation failure:
 *   {
 *     success: false,
 *     error: {
 *       code: "VALIDATION_ERROR",
 *       message: "Please fix the errors below",
 *       fieldErrors: { phone: "Phone number must be at least 10 digits", ... }
 *     }
 *   }
 *
 * Drop-in replacement: same signature as zValidator for the common two-arg case.
 */
export function zv<T extends ZodSchema, Target extends keyof ValidationTargets>(
	target: Target,
	schema: T,
) {
	return zValidator(target, schema, (result, c) => {
		if (!result.success) {
			const issues = (result.error as { issues: ZodIssue[] }).issues;
			return c.json(
				{
					success: false,
					error: {
						code: "VALIDATION_ERROR",
						message: "Please fix the errors below",
						fieldErrors: flattenFieldErrors(issues),
					},
				},
				400,
			);
		}
	});
}
 
function flattenFieldErrors(issues: ZodIssue[]): Record<string, string> {
	const fieldErrors: Record<string, string> = {};
	for (const issue of issues) {
		const path = issue.path.join(".");
		const key = path || "_root";
		// Keep the first message per field — good enough for inline UX.
		Eif (!fieldErrors[key]) {
			fieldErrors[key] = issue.message;
		}
	}
	return fieldErrors;
}