From c11aa862f71822e40b286084ca3ae4f9a05cc9cd Mon Sep 17 00:00:00 2001 From: Kai Gritun Date: Mon, 9 Feb 2026 23:19:05 -0500 Subject: [PATCH] fix(openapi-fetch): handle empty error responses when Content-Length header is stripped When a proxy (like Cloudflare) strips the Content-Length header from an error response with no body, the client would return an empty string as the error value, which is falsy and causes issues when checking `if (error)`. This fix applies the same safe handling to error responses that already exists for success responses - when Content-Length is absent and the body is empty, return undefined for the error value to be consistent with 204 and Content-Length: 0 handling. Fixes #2574 --- packages/openapi-fetch/src/index.js | 13 ++++-- .../test/http-methods/delete.test.ts | 46 +++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/packages/openapi-fetch/src/index.js b/packages/openapi-fetch/src/index.js index 9c0de61f9..354ccc817 100644 --- a/packages/openapi-fetch/src/index.js +++ b/packages/openapi-fetch/src/index.js @@ -260,12 +260,17 @@ export default function createClient(clientOptions) { return { data: await getResponseData(), response }; } - // handle errors - let error = await response.text(); + // handle errors (use text() when no content-length to safely handle empty bodies from proxies) + const raw = await response.text(); + if (!raw) { + // empty error body - return undefined to be consistent with status 204 handling + return { error: undefined, response }; + } + let error = raw; try { - error = JSON.parse(error); // attempt to parse as JSON + error = JSON.parse(raw); // attempt to parse as JSON } catch { - // noop + // noop - keep as raw text } return { error, response }; } diff --git a/packages/openapi-fetch/test/http-methods/delete.test.ts b/packages/openapi-fetch/test/http-methods/delete.test.ts index 0ecfa97c6..16979ca7a 100644 --- a/packages/openapi-fetch/test/http-methods/delete.test.ts +++ b/packages/openapi-fetch/test/http-methods/delete.test.ts @@ -46,4 +46,50 @@ describe("DELETE", () => { // assert error is empty expect(error).toBeUndefined(); }); + + test("handles error response with empty body when Content-Length header is stripped by proxy", async () => { + // Simulate proxy stripping Content-Length header from an empty error response + const client = createObservedClient( + {}, + async () => new Response(null, { status: 500 }), // No Content-Length header + ); + const { data, error, response } = await client.DELETE("/tags/{name}", { + params: { + path: { name: "Tag" }, + }, + }); + + // assert data is undefined for error response + expect(data).toBeUndefined(); + + // assert error is undefined for empty body (consistent with 204 and Content-Length: 0 handling) + expect(error).toBeUndefined(); + + // assert response status is preserved + expect(response.status).toBe(500); + expect(response.ok).toBe(false); + }); + + test("handles success response with empty body when Content-Length header is stripped by proxy", async () => { + // Simulate proxy stripping Content-Length header from an empty success response + const client = createObservedClient( + {}, + async () => new Response(null, { status: 200 }), // No Content-Length header + ); + const { data, error, response } = await client.DELETE("/tags/{name}", { + params: { + path: { name: "Tag" }, + }, + }); + + // assert data is undefined for empty body + expect(data).toBeUndefined(); + + // assert error is undefined for success response + expect(error).toBeUndefined(); + + // assert response status is preserved + expect(response.status).toBe(200); + expect(response.ok).toBe(true); + }); });