From 16cdab3ad0e55f6dfa97d4b09d5de031e7a600a9 Mon Sep 17 00:00:00 2001 From: iroiro147 Date: Thu, 16 Jul 2026 08:27:24 +0530 Subject: [PATCH] fix(sdk): return typed errors for malformed JSON responses Signed-off-by: iroiro147 --- .changeset/success-json-guard.md | 5 +++ packages/sdk/src/http/index.test.ts | 52 +++++++++++++++++++++++++++++ packages/sdk/src/http/index.ts | 7 +++- 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 .changeset/success-json-guard.md diff --git a/.changeset/success-json-guard.md b/.changeset/success-json-guard.md new file mode 100644 index 00000000..248dc22b --- /dev/null +++ b/.changeset/success-json-guard.md @@ -0,0 +1,5 @@ +--- +"@upstash/context7-sdk": patch +--- + +Convert empty or malformed successful JSON responses into a typed `Context7Error` instead of exposing a native `SyntaxError`. Valid JSON responses and retry behavior are unchanged. diff --git a/packages/sdk/src/http/index.test.ts b/packages/sdk/src/http/index.test.ts index 1d0d0209..2b42d566 100644 --- a/packages/sdk/src/http/index.test.ts +++ b/packages/sdk/src/http/index.test.ts @@ -78,4 +78,56 @@ describe("HttpClient error handling", () => { expect(error).toBeInstanceOf(Context7Error); expect(error.message).toBe("Service Unavailable"); }); + + test("throws Context7Error (not SyntaxError) on empty application/json success body", async () => { + mockFetch( + new Response("", { + status: 200, + headers: { "content-type": "application/json" }, + }) + ); + + const error = await newClient() + .request({ path: ["search"] }) + .catch((e) => e); + + expect(error).toBeInstanceOf(Context7Error); + expect(error).not.toBeInstanceOf(SyntaxError); + expect(error.message).toBe("Failed to parse JSON response from Context7 API"); + expect(fetch).toHaveBeenCalledTimes(1); + }); + + test("throws Context7Error (not SyntaxError) on malformed application/json success body", async () => { + mockFetch( + new Response("{ invalid json }", { + status: 200, + headers: { "content-type": "application/json" }, + }) + ); + + const error = await newClient() + .request({ path: ["search"] }) + .catch((e) => e); + + expect(error).toBeInstanceOf(Context7Error); + expect(error).not.toBeInstanceOf(SyntaxError); + expect(error.message).toBe("Failed to parse JSON response from Context7 API"); + expect(fetch).toHaveBeenCalledTimes(1); + }); + + test("returns parsed result on valid application/json success body", async () => { + mockFetch( + new Response(JSON.stringify({ ok: true, items: [1, 2, 3] }), { + status: 200, + headers: { "content-type": "application/json; charset=utf-8" }, + }) + ); + + const response = await newClient().request<{ ok: boolean; items: number[] }>({ + path: ["search"], + }); + + expect(response.result).toEqual({ ok: true, items: [1, 2, 3] }); + expect(fetch).toHaveBeenCalledTimes(1); + }); }); diff --git a/packages/sdk/src/http/index.ts b/packages/sdk/src/http/index.ts index 3f453eae..f5436a23 100644 --- a/packages/sdk/src/http/index.ts +++ b/packages/sdk/src/http/index.ts @@ -179,7 +179,12 @@ export class HttpClient implements Requester { const contentType = res.headers.get("content-type"); if (contentType?.includes("application/json")) { - const body = await res.json(); + let body: unknown; + try { + body = await res.json(); + } catch { + throw new Context7Error("Failed to parse JSON response from Context7 API"); + } return { result: body as TResult }; } else { const text = await res.text();