From c7561994873099deb07e0b1f7bc68a8a96e509ca Mon Sep 17 00:00:00 2001 From: Parth Bansal Date: Wed, 10 Jun 2026 10:21:52 +0000 Subject: [PATCH] ApiError surfaces the decoded response body as its message for non-JSON / non-conforming error bodies Today a non-JSON error response (proxy HTML, binary bodies) or one that fails schema validation yields an empty ApiError message, so the failure is opaque. fromHttpError now decodes the body once and uses that decoded string as the message in both fallback paths (JSON.parse throws; schema validation fails) instead of an empty string. The code, the empty-body branch, and the verbatim error_code mapping are unchanged. This is a hand-written core change only; no generator change is needed. Note: surfacing the decoded body as the message is a small divergence from the Go SDK's apierr.APIError, which leaves it empty. Co-authored-by: Isaac --- packages/core/src/apierror/apierror.ts | 19 ++++++++++++------- packages/core/tests/apierror/apierror.test.ts | 8 ++++---- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/core/src/apierror/apierror.ts b/packages/core/src/apierror/apierror.ts index eff5ca52..fcb585dd 100644 --- a/packages/core/src/apierror/apierror.ts +++ b/packages/core/src/apierror/apierror.ts @@ -141,17 +141,20 @@ export class ApiError extends Error { }); } - // Decode the body to a string for JSON parsing. + // Decode the body once; it doubles as the JSON source and as a readable + // fallback message for non-JSON or non-conforming bodies. + const decoded = new TextDecoder().decode(body); + let parsed: unknown; try { - parsed = JSON.parse(new TextDecoder().decode(body)); + parsed = JSON.parse(decoded); } catch (e: unknown) { - // The JSON error is simply swallowed, this typically happens when the - // error does not come directly from a Databricks API. A typical example - // is when the error is returned by a proxy. + // The body is not JSON; this typically happens when the error does not + // come directly from a Databricks API (e.g. a proxy). Surface the decoded + // body as the message so the failure is debuggable instead of empty. return new ApiError({ code: Code.UNKNOWN, - message: '', + message: decoded, details: emptyDetails, httpStatusCode: statusCode, httpHeader: header, @@ -162,9 +165,11 @@ export class ApiError extends Error { const result = errorResponseSchema.safeParse(parsed); if (!result.success) { + // The body parsed as JSON but does not match the error schema. Surface + // the decoded body as the message so the failure is debuggable. return new ApiError({ code: Code.UNKNOWN, - message: '', + message: decoded, details: emptyDetails, httpStatusCode: statusCode, httpHeader: header, diff --git a/packages/core/tests/apierror/apierror.test.ts b/packages/core/tests/apierror/apierror.test.ts index 4802fcdc..063bcfca 100644 --- a/packages/core/tests/apierror/apierror.test.ts +++ b/packages/core/tests/apierror/apierror.test.ts @@ -160,22 +160,22 @@ describe('fromHttpError', () => { }), }, { - desc: 'HTML body', + desc: 'HTML body decoded into message', statusCode: 502, body: encode('Bad Gateway'), want: new ApiError({ code: Code.UNKNOWN, - message: '', + message: 'Bad Gateway', details: emptyDetails, }), }, { - desc: 'malformed JSON', + desc: 'malformed JSON decoded into message', statusCode: 400, body: encode('{not valid json'), want: new ApiError({ code: Code.UNKNOWN, - message: '', + message: '{not valid json', details: emptyDetails, }), },