Skip to content

fix: wrap decoder exceptions as DecodeError at seam B#32

Merged
lesnik512 merged 10 commits into
mainfrom
worktree-decoder-error
Jun 8, 2026
Merged

fix: wrap decoder exceptions as DecodeError at seam B#32
lesnik512 merged 10 commits into
mainfrom
worktree-decoder-error

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Summary

  • Closes the gap where pydantic.ValidationError / msgspec.ValidationError / msgspec.DecodeError escaped except httpware.ClientError when response_model= was set.
  • New httpware.DecodeError(ClientError) carrying response, model, original (kwargs-only init, __reduce__ for pickle parity). Direct child of ClientError, sibling of StatusError / TransportError / etc.
  • Both Client.send and AsyncClient.send now wrap the _decoder.decode(...) call: try: ... except Exception as exc: raise DecodeError(...) from exc. _dispatch stays outside the try — transport/status errors are unaffected.
  • ResponseDecoder.decode protocol unchanged; docstring grows one sentence documenting the seam wrap. PydanticDecoder / MsgspecDecoder unchanged.

Spec: planning/specs/2026-06-07-decoder-error-design.md
Plan: planning/plans/2026-06-07-decoder-error-plan.md

Behavior change

Consumers catching pydantic.ValidationError (or msgspec.*) directly downstream of client.send(..., response_model=...) will no longer match — those exceptions are now wrapped in DecodeError. Switch to except httpware.DecodeError or the broader except httpware.ClientError. The leaked exceptions weren't a documented contract, so no deprecation pass. Target release: 0.8.1.

Test Plan

  • just test — 382 tests pass, 100% coverage
  • just lint-ci — clean
  • CI-enforced invariants hold: no httpx2._ private API, no from __future__ import annotations, no print()
  • Public API smoke: httpware.DecodeError resolves; issubclass(DecodeError, ClientError) is True
  • Both sync and async send paths verified through PydanticDecoder (5 tests in test_client_response_model.py) and MsgspecDecoder (1 seam test in test_decoders_msgspec.py)
  • Pickle round-trip via __reduce__ covered (test_decode_error_pickleable)

🤖 Generated with Claude Code

lesnik512 and others added 10 commits June 7, 2026 23:50
Closes the gap where pydantic.ValidationError / msgspec.ValidationError
escaped `except httpware.ClientError` when `response_model=` was set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drives the spec at planning/specs/2026-06-07-decoder-error-design.md
into 9 bite-sized tasks: errors.py class, public re-export, both
send() wraps, msgspec seam parity, protocol docstring, errors.md +
engineering.md + README docs, final verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DecodeError is a direct child of ClientError carrying the response,
model, and original library exception. Construction-only here; the
client.send wrap follows in the next commit.
Adds DecodeError to httpware.__init__'s errors import block and __all__,
plus the explicit expected-exports test. No behavior change yet.
Both Client.send and AsyncClient.send now translate any Exception
raised by the active ResponseDecoder into httpware.DecodeError, so
`except httpware.ClientError` covers the response_model= path
uniformly regardless of which decoder is wired in.

Drops the previous test_decoder_validation_error_propagates_unwrapped
case which encoded the now-fixed leak.
Seam-level test wires MsgspecDecoder into AsyncClient and asserts a
malformed-JSON response still surfaces as httpware.DecodeError with
exc.original carrying the underlying msgspec exception.
One-sentence addition: implementers can raise whatever their backing
library raises; Client.send / AsyncClient.send translate to
httpware.DecodeError at the seam.
Adds DecodeError to the exception-tree diagram and a new subsection
covering when it's raised, what fields it carries, and a minimal
except snippet.
Updates the Seam B contract to spell out the wrap, and adds a
paragraph to the §4 exception contract describing when DecodeError
is raised and what fields it carries.
One-line addition: response_model= decode failures raise
httpware.DecodeError (a ClientError subclass), so the standard
except httpware.ClientError catches them.
@lesnik512 lesnik512 self-assigned this Jun 8, 2026
@lesnik512 lesnik512 merged commit eac4a79 into main Jun 8, 2026
5 checks passed
@lesnik512 lesnik512 deleted the worktree-decoder-error branch June 8, 2026 04:29
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.

1 participant