From 9cae11d94ad9a9647f3416ec79b5db1e70feaedc Mon Sep 17 00:00:00 2001 From: alex mitre Date: Wed, 4 Mar 2026 11:02:07 -0600 Subject: [PATCH] fix(sdk): handle non-JSON error responses in HttpClient --- packages/sdk/src/http/index.test.ts | 51 +++++++++++++++++++++++++++++ packages/sdk/src/http/index.ts | 11 +++++-- 2 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 packages/sdk/src/http/index.test.ts diff --git a/packages/sdk/src/http/index.test.ts b/packages/sdk/src/http/index.test.ts new file mode 100644 index 00000000..368cbf97 --- /dev/null +++ b/packages/sdk/src/http/index.test.ts @@ -0,0 +1,51 @@ +import { afterEach, describe, expect, test, vi } from "vitest"; +import { HttpClient } from "./index"; +import { Context7Error } from "@error"; + +describe("HttpClient error handling", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + test("throws Context7Error for non-JSON error responses", async () => { + vi.spyOn(globalThis, "fetch").mockResolvedValue( + new Response("Bad gateway", { + status: 502, + statusText: "Bad Gateway", + headers: { "content-type": "text/html" }, + }) + ); + + const client = new HttpClient({ + baseUrl: "https://example.com/api", + retry: false, + }); + + try { + await client.request({ path: ["v2", "libs", "search"] }); + throw new Error("Expected request to throw"); + } catch (error) { + expect(error).toBeInstanceOf(Context7Error); + expect((error as Error).message).toBe("Bad Gateway"); + } + }); + + test("prefers API error message when response body is JSON", async () => { + vi.spyOn(globalThis, "fetch").mockResolvedValue( + new Response(JSON.stringify({ error: "rate limit exceeded" }), { + status: 429, + statusText: "Too Many Requests", + headers: { "content-type": "application/json" }, + }) + ); + + const client = new HttpClient({ + baseUrl: "https://example.com/api", + retry: false, + }); + + await expect(client.request({ path: ["v2", "libs", "search"] })).rejects.toMatchObject({ + message: "rate limit exceeded", + }); + }); +}); diff --git a/packages/sdk/src/http/index.ts b/packages/sdk/src/http/index.ts index 10d7e33f..4d1a1bb0 100644 --- a/packages/sdk/src/http/index.ts +++ b/packages/sdk/src/http/index.ts @@ -169,8 +169,15 @@ export class HttpClient implements Requester { } if (!res.ok) { - const errorBody = (await res.json()) as { error?: string; message?: string }; - throw new Context7Error(errorBody.error || errorBody.message || res.statusText); + let errorBody: { error?: string; message?: string } | undefined; + + try { + errorBody = (await res.json()) as { error?: string; message?: string }; + } catch { + // Non-JSON error responses should still throw a typed Context7Error. + } + + throw new Context7Error(errorBody?.error || errorBody?.message || res.statusText); } const contentType = res.headers.get("content-type");