fix(errors): surface Harper 5 RFC 9457 problem details in error toasts - #1598
Conversation
There was a problem hiding this comment.
Code Review
This pull request adds support for RFC 9457 Problem Details error formats introduced in Harper 5. Specifically, it updates 'isEmailNotVerifiedError' to check the 'title' field in addition to 'error' and 'message', and enhances the global 'errorHandler' in 'queryClient.ts' to map 'code' to the toast title and 'title' (plus optional 'detail') to the description, while preventing incorrect colon-splitting. Comprehensive unit tests have been added to verify these new error-handling behaviors. I have no additional feedback to provide as the implementation is clean and well-tested.
Harper 5 REST errors changed from '$Code: $message' strings to RFC 9457
Problem Details objects ({ type, code, title, status, detail, instance }),
so against a v5 central-manager every error toast fell through to the
generic 'We had some trouble!'. Map code -> toast title and
title (+ detail) -> description, without colon-splitting the title.
Same class: isEmailNotVerifiedError only read error/message, so on v5
the unverified-email login rejection lost its verification redirect —
now also checks title.
48c2831 to
1a072f0
Compare
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||
DavidCockerill
left a comment
There was a problem hiding this comment.
Approving — nothing to change.
The mapping is deliberately non-literal and that's the right call. Harper's REST.ts builds code = error.code ?? error.constructor.name and title = error.message, so code → heading / title (+ detail) → description produces a sensible toast; an RFC-purist mapping would have put a full sentence in the heading and left the body empty. The splitTitleFromMsg = false suppression is the subtle part and it's correct — without it "Plan not found: plan-123" gets sliced at the colon, and you pinned exactly that case.
I traced 23 error shapes through the new handler rather than just the happy ones — v5 problem details (including a numeric code and an empty title), v5 ops-API {error}, v4 string bodies, an nginx HTML 502, network/CORS/timeout, undefined/null/{}/array/Blob, a non-string title, both-title-and-error, and a circular detail. No blank toast, no [object Object], no raw JSON blob, and nothing throws inside the handler on any of them (errorText catches circular JSON, data.title is narrowed to string before the template literal, and errorMsg is provably a string at the .includes(':') guard on every path).
Two things I specifically went looking for and didn't find:
- The verification gate still matches. This was the real risk — a detection that quietly stops matching means unverified users get a generic error instead of the resend flow. The change is purely additive (
titleappended last, existing fields win), and I checked the live wire format rather than the intent: central-manager pinsharperdb: ^4.7.29, so deployed CM serializeserrorToString(error)and the rejection arrives as a string, taking the untouched branch entirely. On v5 the new branch matches the same text. No fail-open either — of the three 403s reachable fromPOST /Login, only the intended one matches/verif/i. - No new sensitive-data exposure.
instance(the request URL, the one field that could carry a token in a query string) andtypeare deliberately not surfaced, and the logging path is unchanged.
One thing worth knowing but explicitly not yours to fix here: a proxy returning text/html still renders the whole document — tags and nginx version banner — into the toast, via the typeof data === 'string' branch this PR only reindents. It's now the last error shape without sane handling. Happy to file that separately if you want it tracked.
Reviewed by Claude Opus 5 for @DavidCockerill.
What
Harper 5 changed REST error responses from
"Code: message"strings to RFC 9457 Problem Details objects:{ "type": "error:ClientError", "code": "ClientError", "title": "Not allowed", "status": 403, "instance": "/Cluster/clu-123" }The global
errorHandleronly looked atdata.error/data.message, so against a Harper 5 central-manager every error toast fell through to the generic "We had some trouble!".Changes
queryClient.ts— recognize a Problem Details body:code→ toast title,title(+detailwhen present) → description. The title is a plain sentence that can itself contain colons ("Plan not found: plan-123"), so the legacy "Title: detail" colon-split is skipped for this shape. v4 string/error/messagehandling is unchanged, so stage/prod CMs still on 4.x are unaffected.isEmailNotVerifiedError.ts— same class of bug found by sweeping for other CM error-body parsing: the unverified-email login detection only readerror/message, so on a v5 CM an unverified user would get a dead-end toast instead of the email-verification redirect. Now also checkstitle.Verification
server/REST.tsProblem Details construction):code = error.code ?? constructor.name,title = error.message,detailonly present when set.— devain (Claude)