fix(errors): warn on raw stringify fallback

This commit is contained in:
ZacharyZhang-NY committed 2026-04-22 19:04:53 -04:00
1 parent ee1ea4fd8c
commit 1883d9d9d3
2 files changed
+57 -1

No files matched your search

+15 -1
View File
@@ -41,6 +41,8 @@
* and ideally the process.
*/
import log from "electron-log/main.js";
export type ErrorKind = "recoverable" | "user_input" | "upstream" | "fatal";
export interface AppErrorMeta {
@@ -146,6 +148,8 @@ export class FatalError extends AppError {
}
}
let rawStringifyFallbackWarned = false;
/**
* Wrap an unknown thrown value into an AppError.
*
@@ -199,14 +203,24 @@ function stringifyRaw(value: unknown): string {
}
try {
return JSON.stringify(value);
} catch {
} catch (error) {
// The raw value is a cyclic / un-serialisable object. `String(value)`
// falls back to "[object Object]" which is still better than losing
// the fact that something was thrown.
warnRawStringifyFallback(error);
return String(value);
}
}
function warnRawStringifyFallback(error: unknown): void {
if (rawStringifyFallbackWarned) return;
rawStringifyFallbackWarned = true;
log.warn(
"[errors] stringifyRaw failed; using String(value) for raw thrown value.",
error instanceof Error ? error.message : String(error),
);
}
/**
* Convenience narrowing predicate. Intentional: avoids forcing every
* consumer to re-import `AppError` just to write an `instanceof` guard.
+42
View File
@@ -0,0 +1,42 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("electron-log/main.js", () => ({
default: {
warn: vi.fn(),
},
}));
import log from "electron-log/main.js";
import { classifyUnknownError } from "../../src/main/errors/taxonomy";
const warn = vi.mocked(log.warn);
describe("error taxonomy", () => {
beforeEach(() => {
warn.mockClear();
});
it("warns once when raw thrown values cannot be JSON stringified", () => {
const raw: { self?: unknown } = {};
raw.self = raw;
const first = classifyUnknownError(raw, {
kind: "recoverable",
code: "test.raw.cyclic",
message: "Cyclic raw value.",
});
const second = classifyUnknownError(raw, {
kind: "recoverable",
code: "test.raw.cyclic",
message: "Cyclic raw value.",
});
expect(first.context?.raw).toBe("[object Object]");
expect(second.context?.raw).toBe("[object Object]");
expect(warn).toHaveBeenCalledTimes(1);
expect(warn).toHaveBeenCalledWith(
"[errors] stringifyRaw failed; using String(value) for raw thrown value.",
expect.stringContaining("circular"),
);
});
});