Skip to content

fix(api): declare the real v1 error envelope and the SSE content type (#116) - #136

Merged
pparage merged 1 commit into
devfrom
fix/v1-response-contracts
Aug 6, 2026
Merged

fix(api): declare the real v1 error envelope and the SSE content type (#116)#136
pparage merged 1 commit into
devfrom
fix/v1-response-contracts

Conversation

@pparage

@pparage pparage commented Aug 6, 2026

Copy link
Copy Markdown
Member

Closes #116. Both findings originally from Codex review on #115.

The committed spec bootstraps the Kong gateway and is what clients would be generated from, so a wrong declaration is not cosmetic — it produces clients that mis-parse real responses.

422 shape

app/core/errors installs a RequestValidationError handler returning the Range42 envelope:

{"error": "...", "message": "...", "code": "...", "details": [...], "trace_id": "...", "timestamp": "..."}

…while all 48 v1 operations advertised FastAPI's default {"detail": [...]}.

Added ErrorEnvelope (mirroring _envelope()) and declared it once on the aggregate v1 router via responses=, so every operation inherits it rather than 48 routes each repeating the same block — and a new v1 route gets it automatically.

SSE content type

/v1/deployments/{deployment_id}/events returns EventSourceResponse, so its body is text/event-stream. The spec said application/json, which would have a generated consumer awaiting a body that never completes instead of opening a stream. Now declared with response_class plus an explicit text/event-stream response.

Tests

Three, all failing against dev:

FAILED test_a_real_422_matches_the_declared_schema
FAILED test_no_v1_operation_still_declares_the_fastapi_default
FAILED test_events_stream_is_declared_as_sse

The first is the important one: it POSTs an invalid body, reads the real 422, and asserts its keys match the declared schema's properties. Asserting on the spec alone would let the declaration and the handler drift together — which is exactly how this bug existed in the first place.

Deliberately not changed

The 48 v0 operations still advertise the default 422. They do not go through the v1 handlers, and they are on the decom track (#113 removed a large chunk already), so aligning them would be churn on surface we intend to delete. Verified the remaining HTTPValidationError references are v0-only.

491 passed, ruff clean, spec regenerated — and #134's drift guard now enforces that.

…#116)

Two response contracts in the committed spec did not match what the app
returns. Since that spec bootstraps the Kong gateway and is what clients
would be generated from, both produce clients that mis-parse real
responses.

422: app/core/errors installs a RequestValidationError handler returning
the Range42 envelope (error/message/code/details/trace_id/timestamp),
but every v1 operation advertised FastAPI's default {"detail": [...]}.
Added ErrorEnvelope mirroring _envelope() and declared it once on the
aggregate v1 router, so all 48 operations inherit it rather than each
route repeating itself.

SSE: /v1/deployments/{id}/events returns EventSourceResponse, so its
body is text/event-stream. It advertised application/json, which would
have a generated consumer awaiting a body that never completes instead
of opening a stream.

The first test asserts a real 422 body and the declared schema have the
same keys — asserting on the spec alone would let both drift together.

v0 still advertises the default 422; those routes do not go through the
v1 handlers and are decom-track anyway, so they are deliberately left.
@pparage
pparage merged commit 80ad1f9 into dev Aug 6, 2026
3 checks passed
@pparage
pparage deleted the fix/v1-response-contracts branch August 6, 2026 11:39
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