Skip to content

fix(client): surface the API error message on a failed request (#33) - #55

Open
DAmensah27 wants to merge 2 commits into
jamiedavenport:mainfrom
DAmensah27:fix/client-surface-api-error-message
Open

fix(client): surface the API error message on a failed request (#33)#55
DAmensah27 wants to merge 2 commits into
jamiedavenport:mainfrom
DAmensah27:fix/client-surface-api-error-message

Conversation

@DAmensah27

Copy link
Copy Markdown

Closes #33.

What & why. createContentClient's request in published/client/src/content.ts threw on any non-ok response with the status code alone:

throw new Error(`Stet request ${path} failed with status ${response.status}.`);

But the API answers a failure with a described body — oRPC serializes a thrown error to JSON carrying a human message that internal/api/src/contract.ts writes for each case (UNAUTHORIZED explains the missing x-api-key header; there are equally specific messages for QUOTA_EXCEEDED, RATE_LIMITED, NOT_FOUND). None of that reached the developer: a revoked key, an exhausted quota, and a misspelled slug all surfaced as a bare number — exactly when a typed client should help most.

What this PR does.

  • Adds an errorMessage(response) helper that reads the body, returns its message string when present, and returns undefined for an empty/non-JSON body or one without a message.
  • On a non-ok response, request now appends that message to the thrown error, and falls back to the status-only message otherwise — so opaque failures (e.g. a proxy's HTML error page) don't regress.

Tests. Extends content.test.ts with an errorFetch helper and four cases: the message is surfaced for both get() and list(), and the fallback holds for a non-JSON body and for a JSON body carrying no message.

Verification. @stetcms/client suite → 18 passed (14 existing + 4 new); tsc --noEmit clean; vp check reports all files correctly formatted with no lint or type errors.

…davenport#33)

createContentClient's request threw on any non-ok response with the
status code alone, so a revoked key, an exhausted quota, and a misspelled
slug all surfaced as a bare number -- discarding the described error body
the API returns (oRPC serializes a human `message` for UNAUTHORIZED,
QUOTA_EXCEEDED, RATE_LIMITED, NOT_FOUND).

Read the response body, pull out `message`, and append it to the thrown
error. Fall back to the status-only message when the body is empty, not
JSON, or carries no message, so nothing regresses for opaque failures.

Adds error-path tests (message surfaced for get() and list(); fallback
for a non-JSON body and for a body with no message). Client suite: 18
passed; tsc and vp check clean.
Comment on lines +146 to +163
async function errorMessage(response: Response): Promise<string | undefined> {
let body: unknown;
try {
body = await response.json();
} catch {
// Empty or non-JSON body — the status-only message is the best we have.
return undefined;
}
if (typeof body !== 'object' || body === null) {
return undefined;
}
const message = (body as { message?: unknown }).message;
if (typeof message !== 'string') {
return undefined;
}
const trimmed = message.trim();
return trimmed.length > 0 ? trimmed : undefined;
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a look at createORPCErrorFromJson and isORPCErrorJson

…davenport#33 review)

Address review feedback on jamiedavenport#55: instead of hand-reading `message` off the
response body, validate and read it with `isORPCErrorJson` /
`createORPCErrorFromJson` from `@orpc/client`. This matches the server's
error shape exactly (`defined`/`code`/`status`/`message`, no stray keys) and
reuses oRPC's `code`-based message fallback rather than reimplementing it.

Tests updated to the real oRPC error wire shape (via an `orpcError` helper);
the fallback case now covers a JSON body that isn't an oRPC error. Client
suite 18 passed; tsc and vp check clean.
@DAmensah27

Copy link
Copy Markdown
Author

Good call — switched errorMessage to use oRPC's own isORPCErrorJson + createORPCErrorFromJson from @orpc/client instead of hand-reading the body. That validates the full error shape (defined/code/status/message, no stray keys) and reuses the code-based message fallback, so it stays in sync with the server. Updated the tests to the real oRPC error wire shape (added an orpcError helper) and made the fallback case cover a JSON body that isn't an oRPC error. 18 tests pass; tsc and vp check clean. Thanks for the pointer!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Surface the API's error message when a content request fails

2 participants