fix(client): surface the API error message on a failed request (#33) - #55
Open
DAmensah27 wants to merge 2 commits into
Open
fix(client): surface the API error message on a failed request (#33)#55DAmensah27 wants to merge 2 commits into
DAmensah27 wants to merge 2 commits into
Conversation
…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.
jamiedavenport
requested changes
Jul 31, 2026
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; | ||
| } |
Owner
There was a problem hiding this comment.
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.
Author
|
Good call — switched |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #33.
What & why.
createContentClient'srequestinpublished/client/src/content.tsthrew on any non-ok response with the status code alone:But the API answers a failure with a described body — oRPC serializes a thrown error to JSON carrying a human
messagethatinternal/api/src/contract.tswrites for each case (UNAUTHORIZEDexplains the missingx-api-keyheader; there are equally specific messages forQUOTA_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.
errorMessage(response)helper that reads the body, returns itsmessagestring when present, and returnsundefinedfor an empty/non-JSON body or one without amessage.requestnow 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.tswith anerrorFetchhelper and four cases: the message is surfaced for bothget()andlist(), and the fallback holds for a non-JSON body and for a JSON body carrying nomessage.Verification.
@stetcms/clientsuite → 18 passed (14 existing + 4 new);tsc --noEmitclean;vp checkreports all files correctly formatted with no lint or type errors.