Skip to content

Commit 574996f

Browse files
committed
fix: hide the error stack properly
1 parent 5ea5ddf commit 574996f

2 files changed

Lines changed: 76 additions & 3 deletions

File tree

src/error.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { describe, expect, it } from "vitest";
2+
import { APIError } from "./error";
3+
4+
describe("APIError", () => {
5+
it("should throw correct stack", () => {
6+
const error = new APIError("INTERNAL_SERVER_ERROR", {
7+
message: "Test error",
8+
});
9+
expect(error.stack).toMatchInlineSnapshot(`"APIError: Test error"`);
10+
11+
function testError() {
12+
throw new APIError("INTERNAL_SERVER_ERROR", {
13+
message: "Test error in function",
14+
});
15+
}
16+
17+
function deepTestError() {
18+
testError();
19+
}
20+
21+
expect(() => deepTestError()).toThrowErrorMatchingInlineSnapshot(
22+
`[APIError: Test error in function]`,
23+
);
24+
});
25+
});

src/error.ts

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,42 @@
1+
// https://github.com/nodejs/node/blob/360f7cc7867b43344aac00564286b895e15f21d7/lib/internal/errors.js#L246C1-L261C2
2+
function isErrorStackTraceLimitWritable() {
3+
const desc = Object.getOwnPropertyDescriptor(Error, "stackTraceLimit");
4+
if (desc === undefined) {
5+
return Object.isExtensible(Error);
6+
}
7+
8+
return Object.prototype.hasOwnProperty.call(desc, "writable")
9+
? desc.writable
10+
: desc.set !== undefined;
11+
}
12+
13+
// https://github.com/nodejs/node/blob/360f7cc7867b43344aac00564286b895e15f21d7/lib/internal/errors.js#L411-L432
14+
function makeErrorForHideStackFrame<B extends new (...args: any[]) => Error>(
15+
Base: B,
16+
clazz: any,
17+
): B {
18+
class HideStackFramesError extends Base {
19+
constructor(...args: any[]) {
20+
if (isErrorStackTraceLimitWritable()) {
21+
const limit = Error.stackTraceLimit;
22+
Error.stackTraceLimit = 0;
23+
super(...args);
24+
Error.stackTraceLimit = limit;
25+
} else {
26+
super(...args);
27+
}
28+
}
29+
30+
// This is a workaround for wpt tests that expect that the error
31+
// constructor has a `name` property of the base class.
32+
get ["constructor"]() {
33+
return clazz;
34+
}
35+
}
36+
37+
return HideStackFramesError;
38+
}
39+
140
export const _statusCode = {
241
OK: 200,
342
CREATED: 201,
@@ -116,19 +155,27 @@ export type Status =
116155
| 510
117156
| 511;
118157

119-
export class APIError extends Error {
158+
class InternalAPIError extends Error {
120159
constructor(
121160
public status: keyof typeof _statusCode | Status = "INTERNAL_SERVER_ERROR",
122161
public body:
123162
| ({
124163
message?: string;
125164
code?: string;
165+
cause?: unknown;
126166
} & Record<string, any>)
127167
| undefined = undefined,
128168
public headers: HeadersInit = {},
129169
public statusCode = typeof status === "number" ? status : _statusCode[status],
130170
) {
131-
super(body?.message);
171+
super(
172+
body?.message,
173+
body?.cause
174+
? {
175+
cause: body.cause,
176+
}
177+
: undefined,
178+
);
132179
this.name = "APIError";
133180
this.status = status;
134181
this.headers = headers;
@@ -142,6 +189,7 @@ export class APIError extends Error {
142189
...body,
143190
}
144191
: undefined;
145-
this.stack = "";
146192
}
147193
}
194+
195+
export const APIError = makeErrorForHideStackFrame(InternalAPIError, Error);

0 commit comments

Comments
 (0)