Skip to content

Commit efd210e

Browse files
committed
Fix JSON stringify circular structure during serializing
1 parent 66377d2 commit efd210e

File tree

2 files changed

+56
-7
lines changed

2 files changed

+56
-7
lines changed

src/core/cliManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ export class CliManager {
267267
if (Number.isNaN(contentLength)) {
268268
this.output.warn(
269269
"Got invalid or missing content length",
270-
rawContentLength,
270+
rawContentLength ?? "",
271271
);
272272
} else {
273273
this.output.info("Got content length", prettyBytes(contentLength));

src/logging/formatters.ts

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,35 @@ export function formatContentLength(
3232
const len = headers["content-length"];
3333
if (len && typeof len === "string") {
3434
const bytes = parseInt(len, 10);
35-
return isNaN(bytes) ? "(?b)" : `(${prettyBytes(bytes)})`;
35+
return isNaN(bytes) ? "(?B)" : `(${prettyBytes(bytes)})`;
3636
}
3737

3838
// Estimate from data if no header
39-
if (data !== undefined && data !== null) {
40-
const estimated = Buffer.byteLength(JSON.stringify(data), "utf8");
41-
return `(~${prettyBytes(estimated)})`;
39+
40+
if (data === undefined || data === null) {
41+
return `(${prettyBytes(0)})`;
42+
}
43+
44+
if (Buffer.isBuffer(data)) {
45+
return `(${prettyBytes(data.byteLength)})`;
46+
}
47+
if (typeof data === "string" || typeof data === "bigint") {
48+
const bytes = Buffer.byteLength(data.toString(), "utf8");
49+
return `(${prettyBytes(bytes)})`;
50+
}
51+
if (typeof data === "number" || typeof data === "boolean") {
52+
return `(~${prettyBytes(8)})`;
53+
}
54+
55+
if (typeof data === "object") {
56+
const stringified = safeStringify(data);
57+
if (stringified !== null) {
58+
const bytes = Buffer.byteLength(stringified, "utf8");
59+
return `(~${prettyBytes(bytes)})`;
60+
}
4261
}
4362

44-
return `(${prettyBytes(0)})`;
63+
return "(?B)";
4564
}
4665

4766
export function formatUri(
@@ -66,8 +85,38 @@ export function formatHeaders(headers: Record<string, unknown>): string {
6685

6786
export function formatBody(body: unknown): string {
6887
if (body) {
69-
return JSON.stringify(body);
88+
return safeStringify(body) ?? "<invalid body>";
7089
} else {
7190
return "<no body>";
7291
}
7392
}
93+
94+
function safeStringify(data: unknown): string | null {
95+
try {
96+
const seen = new WeakSet();
97+
return JSON.stringify(data, (_key, value) => {
98+
// Handle circular references
99+
if (typeof value === "object" && value !== null) {
100+
if (seen.has(value)) {
101+
return "[Circular]";
102+
}
103+
seen.add(value);
104+
}
105+
106+
// Handle special types that might slip through
107+
if (typeof value === "function") {
108+
return "[Function]";
109+
}
110+
if (typeof value === "symbol") {
111+
return "[Symbol]";
112+
}
113+
if (typeof value === "bigint") {
114+
return value.toString();
115+
}
116+
117+
return value;
118+
});
119+
} catch {
120+
return null;
121+
}
122+
}

0 commit comments

Comments
 (0)