Files
Reactive-Resume/packages/import/src/error.ts
T
bad431b2fc fix(import): auto-detect JSON format and show readable errors (#1) (#3296)
* fix(import): auto-detect JSON format and show readable errors (#1)

Readable import errors, a fail-soft v4 parser, and auto-detect of the JSON format so uploads just work. The format dropdown becomes an optional override. PDF and DOCX (AI) paths are untouched.

* fix(import): address review feedback on the v4 guard and error message

- reactive-resume-v4-json.tsx: reject arrays in isRecord so array-valued
  basics, sections, or metadata no longer pass the v4 shape guard.
- reactive-resume-v4-json.tsx: reuse the guard's error instance in the
  catch arm instead of allocating a duplicate NOT_V4_MESSAGE.
- error.ts: use a singular "Problem" label for root-level Zod issues so
  the message stays grammatical.
- add a regression test for array-valued v4 branches.

* fix(import): preserve selected JSON format

* test(import): cover selected JSON parser

---------

Co-authored-by: MrTig-afk <MrTig-afk@users.noreply.github.com>
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-08-13 23:01:06 +02:00

34 lines
1.4 KiB
TypeScript

import { ZodError } from "zod";
const MAX_LISTED_ISSUES = 3;
const describeIssuePath = (path: ReadonlyArray<PropertyKey>): string =>
path.length > 0 ? path.map((segment) => String(segment)).join(".") : "the document";
/** Builds a short, human-readable summary from a ZodError instead of a raw JSON dump. */
const summarizeZodError = (error: ZodError): string => {
if (error.issues.length === 0) return "The file could not be read as a valid resume.";
const listed = error.issues
.slice(0, MAX_LISTED_ISSUES)
.map((issue) => `${describeIssuePath(issue.path)} (${issue.message})`)
.join(", ");
const remaining = error.issues.length - MAX_LISTED_ISSUES;
const suffix = remaining > 0 ? `, and ${remaining} more field${remaining === 1 ? "" : "s"}` : "";
// Root-level issues have an empty path (rendered as "the document"); use a
// singular label for them so the sentence stays grammatical.
const allAtRoot = error.issues.slice(0, MAX_LISTED_ISSUES).every((issue) => issue.path.length === 0);
const prefix = allAtRoot ? "Problem" : "Problem fields";
return `The file could not be read as a valid resume. ${prefix}: ${listed}${suffix}.`;
};
/** Rethrows a ZodError as a human-readable import error; re-throws other errors as-is. */
export function rethrowAsImportError(error: unknown): never {
if (error instanceof ZodError) {
throw new Error(summarizeZodError(error));
}
throw error;
}