Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions src/cli/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,33 @@ function effectiveWorkspaceUrl(flag?: string): string | undefined {
return flag?.trim() || process.env.SLACK_WORKSPACE_URL?.trim() || undefined;
}

function errorMessage(err: unknown): string {
return err instanceof Error ? err.message : String(err);
export function errorMessage(err: unknown): string {
if (!(err instanceof Error)) {
return String(err);
}
const { message: rootMessage, cause: rootCause } = err;
let message = rootMessage;
let cause: unknown = rootCause;
const seenCauses = new Set<unknown>([err]);
while (cause !== undefined && cause !== null) {
if (seenCauses.has(cause)) {
break;
}
seenCauses.add(cause);
if (cause instanceof AggregateError && cause.errors.length > 0) {
message += `: ${cause.errors.map((e) => (e instanceof Error ? e.message : String(e))).join("; ")}`;
break;
}
if (cause instanceof Error) {
const { message: causeMessage, cause: nextCause } = cause;
message += `: ${causeMessage}`;
cause = nextCause;
continue;
}
message += `: ${String(cause)}`;
break;
}
return message;
}

function parseContentType(value: unknown): "any" | "text" | "image" | "snippet" | "file" {
Expand Down
51 changes: 51 additions & 0 deletions test/error-message.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { describe, expect, test } from "bun:test";
import { errorMessage } from "../src/cli/context.ts";

describe("errorMessage", () => {
test("returns the message for a plain error", () => {
expect(errorMessage(new Error("boom"))).toBe("boom");
});

test("stringifies non-Error values", () => {
expect(errorMessage("boom")).toBe("boom");
});

test("appends a single Error cause", () => {
const err = new Error("fetch failed", { cause: new Error("connect ECONNREFUSED") });
expect(errorMessage(err)).toBe("fetch failed: connect ECONNREFUSED");
});

test("appends AggregateError causes from a failed fetch through a dead proxy", () => {
const cause = new AggregateError(
[
new Error("connect ECONNREFUSED 127.0.0.1:9090"),
new Error("connect ECONNREFUSED ::1:9090"),
],
"ECONNREFUSED",
);
const err = new Error("fetch failed", { cause });
expect(errorMessage(err)).toBe(
"fetch failed: connect ECONNREFUSED 127.0.0.1:9090; connect ECONNREFUSED ::1:9090",
);
});

test("walks multi-level cause chains", () => {
const root = new Error("connect ECONNREFUSED");
const middle = new Error("request failed", { cause: root });
const err = new Error("fetch failed", { cause: middle });
expect(errorMessage(err)).toBe("fetch failed: request failed: connect ECONNREFUSED");
});

test("terminates on a self-referential cause instead of looping forever", () => {
const err = new Error("boom");
err.cause = err;
expect(errorMessage(err)).toBe("boom");
});

test("terminates on a longer cause cycle", () => {
const a = new Error("a");
const b = new Error("b", { cause: a });
a.cause = b;
expect(errorMessage(a)).toBe("a: b");
});
});