UNPKG

@zod/core

Version:

TypeScript-first schema declaration and validation library with static type inference

208 lines (207 loc) 7 kB
import { jsonStringifyReplacer } from "./util.js"; //////////////////////// ERROR CLASS //////////////////////// const ZOD_ERROR = Symbol.for("{{zod.error}}"); export class $ZodError { get message() { return JSON.stringify(this.issues, jsonStringifyReplacer, 2); } constructor(issues) { Object.defineProperty(this, "_tag", { value: ZOD_ERROR, enumerable: false }); Object.defineProperty(this, "name", { value: "$ZodError", enumerable: false }); this.issues = issues; } // @ts-ignore static [Symbol.hasInstance](inst) { return inst?._tag === ZOD_ERROR; } } export function flattenError(error, mapper = (issue) => issue.message) { const fieldErrors = {}; const formErrors = []; for (const sub of error.issues) { if (sub.path.length > 0) { fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || []; fieldErrors[sub.path[0]].push(mapper(sub)); } else { formErrors.push(mapper(sub)); } } return { formErrors, fieldErrors }; } export function formatError(error, _mapper) { const mapper = _mapper || function (issue) { return issue.message; }; const fieldErrors = { _errors: [] }; const processError = (error) => { for (const issue of error.issues) { if (issue.code === "invalid_union") { issue.errors.map((issues) => processError({ issues })); } else if (issue.code === "invalid_key") { processError({ issues: issue.issues }); } else if (issue.code === "invalid_element") { processError({ issues: issue.issues }); } else if (issue.path.length === 0) { fieldErrors._errors.push(mapper(issue)); } else { let curr = fieldErrors; let i = 0; while (i < issue.path.length) { const el = issue.path[i]; const terminal = i === issue.path.length - 1; if (!terminal) { curr[el] = curr[el] || { _errors: [] }; } else { curr[el] = curr[el] || { _errors: [] }; curr[el]._errors.push(mapper(issue)); } curr = curr[el]; i++; } } } }; processError(error); return fieldErrors; } export function treeifyError(error, _mapper) { const mapper = _mapper || function (issue) { return issue.message; }; const result = { errors: [] }; const processError = (error) => { var _a, _b; for (const issue of error.issues) { if (issue.code === "invalid_union") { issue.errors.map((issues) => processError({ issues })); } else if (issue.code === "invalid_key") { processError({ issues: issue.issues }); } else if (issue.code === "invalid_element") { processError({ issues: issue.issues }); } else if (issue.path.length === 0) { result.errors.push(mapper(issue)); } else { let curr = result; let i = 0; while (i < issue.path.length) { const el = issue.path[i]; const terminal = i === issue.path.length - 1; if (typeof el === "string") { curr.properties ?? (curr.properties = {}); (_a = curr.properties)[el] ?? (_a[el] = { errors: [] }); curr = curr.properties[el]; } else { curr.items ?? (curr.items = []); (_b = curr.items)[el] ?? (_b[el] = { errors: [] }); curr = curr.items[el]; } if (terminal) { curr.errors.push(mapper(issue)); } // curr = curr.properties[el]; // const terminal = i === issue.path.length - 1; // if (!curr) { // if (typeof el === "string") { // result.properties = {}; // } else { // (result as any).properties = []; // } // } // // const curr: any = result.properties; // if (!terminal) { // curr[el] = curr[el] || { errors: [] }; // } else { // curr[el] = curr[el] || { errors: [] }; // curr[el].errors.push(mapper(issue)); // } // curr = curr[el]; // curr = curr.properties[el]; i++; } } } }; processError(error); return result; } /** Format a ZodError as a human-readable string in the following form. * * From * * ```ts * ZodError { * issues: [ * { * expected: 'string', * code: 'invalid_type', * path: [ 'username' ], * message: 'Invalid input: expected string' * }, * { * expected: 'number', * code: 'invalid_type', * path: [ 'favoriteNumbers', 1 ], * message: 'Invalid input: expected number' * } * ]; * } * ``` * * to * * ``` * username * ✖ Expected number, received string at "username * favoriteNumbers[0] * ✖ Invalid input: expected number * ``` */ export function toDotPath(path) { const segs = []; for (const seg of path) { if (typeof seg === "number") segs.push(`[${seg}]`); else if (typeof seg === "symbol") segs.push(`["${String(seg)}"]`); else if (seg.includes(".")) segs.push(`["${seg}"]`); else { if (segs.length) segs.push("."); segs.push(seg); } } return segs.join(""); } export function prettifyError(error) { // Create a Map to group issues by path // const issuesMap = new Map<string, string[]>(); const lines = []; // issuesMap.set("", []); // if(error.issues.every(issue => issue.path.length === 0)) { // return error.issues.map(issue => `✖ ${issue.message}`).join("\n"); // } // sort by path length const issues = [...error.issues].sort((a, b) => a.path.length - b.path.length); // Process each issue for (const issue of issues) { lines.push(`✖ ${issue.message}`); if (issue.path?.length) lines.push(` → at ${toDotPath(issue.path)}`); } // Convert Map to formatted string return lines.join("\n"); }