metis-desk/apps/server/src/infrastructure/http/http-error-diagnostic.ts

49 lines
1.9 KiB
TypeScript
Raw Normal View History

export type HttpErrorDiagnostic = Readonly<{
name: string;
code?: string;
errno?: number;
sqlState?: string;
frames: readonly string[];
}>;
/** Extrae datos técnicos sin serializar mensajes, SQL ni propiedades arbitrarias del error. */
export function httpErrorDiagnostics(failure: unknown): readonly HttpErrorDiagnostic[] {
const diagnostics: HttpErrorDiagnostic[] = [];
const visited = new Set<Error>();
let current = failure;
while (current instanceof Error && !visited.has(current) && diagnostics.length < 5) {
visited.add(current);
diagnostics.push(describeError(current));
current =
current.cause instanceof Error
? current.cause
: "parent" in current
? current.parent
: undefined;
}
return diagnostics.length ? diagnostics : [{ name: "NonErrorThrown", frames: [] }];
}
function describeError(failure: Error): HttpErrorDiagnostic {
const code = "code" in failure ? failure.code : undefined;
const errno = "errno" in failure ? failure.errno : undefined;
const sqlState = "sqlState" in failure ? failure.sqlState : undefined;
// El prefijo puede contener un mensaje multilínea: se elimina completo antes de leer los frames.
const prefix = failure.message ? `${failure.name}: ${failure.message}` : failure.name;
const stack = failure.stack?.startsWith(prefix) ? failure.stack.slice(prefix.length) : "";
return {
name: /^[A-Za-z][A-Za-z0-9]*Error$|^Error$/.test(failure.name) ? failure.name : "Error",
...(typeof code === "string" && /^(?:ER_[A-Z0-9_]+|E[A-Z0-9_]+)$/.test(code) ? { code } : {}),
...(typeof errno === "number" && Number.isSafeInteger(errno) ? { errno } : {}),
...(typeof sqlState === "string" && /^[0-9A-Z]{5}$/.test(sqlState) ? { sqlState } : {}),
frames: stack
.split("\n")
.filter((line) => /^\s+at .+:\d+:\d+\)?$/.test(line))
.slice(0, 20)
.map((line) => line.trim()),
};
}