Skip to content

Aggregate body errors[] messages in HttpError.from()#2

Merged
freshlogic merged 12 commits into
mainfrom
errors-aggregation
May 12, 2026
Merged

Aggregate body errors[] messages in HttpError.from()#2
freshlogic merged 12 commits into
mainfrom
errors-aggregation

Conversation

@freshlogic
Copy link
Copy Markdown
Member

Summary

When a response body carries an `errors[]` envelope, `from()` now surfaces every `errors[].message` joined by `; ` as `err.message` instead of the default `"${status} ${statusText}"`.

This is the established convention for APIs that don't rely solely on HTTP status codes to signal failure:

  • GraphQL servers (always 200 OK with `data.errors[]` — every Apollo/urql/graphql-request user hits this)
  • REST APIs like FedEx Rate/Ship that return 200 + `errors[]` for application-level failures
  • JSON:API responses, which use a top-level `errors[]` array

Aggregation runs regardless of HTTP status, so a 200-with-errors response and a 4xx-with-errors response both produce a fully populated `HttpError`. Codes and any other per-error fields stay accessible via `err.json.errors[]`.

Test plan

  • Aggregates errors[] messages into err.message on 200 responses
  • Aggregates errors[] messages on non-2xx responses too
  • Leaves message as `"${status} ${statusText}"` when body has no errors[]
  • All existing tests still pass

🤖 Generated with Claude Code

When a response body carries an errors[] envelope (GraphQL data.errors[],
REST errors[], etc.), surface every errors[].message joined by '; ' as
err.message instead of the default '${status} ${statusText}'. Codes and any
other per-error fields stay accessible via err.json.errors[].

Aggregation runs regardless of HTTP status, so a 200-with-errors response
and a 4xx-with-errors response both produce a fully populated HttpError
when passed to from().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coveralls
Copy link
Copy Markdown

coveralls commented May 12, 2026

Coverage Report for CI Build 25708010537

Coverage remained the same at 100.0%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: 14 of 14 lines across 1 file are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 48
Covered Lines: 48
Line Coverage: 100.0%
Relevant Branches: 13
Covered Branches: 13
Branch Coverage: 100.0%
Branches in Coverage %: Yes
Coverage Strength: 4.83 hits per line

💛 - Coveralls

freshlogic and others added 11 commits May 11, 2026 20:12
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GraphQL's spec mandates errors[].message; JSON:API uses errors[].detail
with title as a short summary. Both are widely used 'errors[]' envelope
shapes, so picking the first present field across the three keeps the
aggregation useful for either without forcing callers to override the
message themselves.

Drops the RFC 9457 reference — Problem Details is a top-level object, not
a nested errors[] envelope, so the aggregation block doesn't apply.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JSON:API title is a category/summary that shouldn't change between
occurrences, so it's a weak per-instance message. Sticking with message
and detail gives clean 2-for-2 coverage of GraphQL and JSON:API.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…pattern

response.clone() throws synchronously when the body has been disturbed, so
wrap the body read in try/catch. After a caller does response.json() and
then needs to throw on an in-body errors envelope, calling from() again no
longer crashes — it returns a baseline HttpError with the status message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@freshlogic freshlogic merged commit c83f7b5 into main May 12, 2026
4 checks passed
@freshlogic freshlogic deleted the errors-aggregation branch May 12, 2026 01:43
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.

2 participants