fix(client): typed multi-response handling with ApiError envelope (closes #8)#9
Merged
Merged
Conversation
…oses #8) Operations that declared multiple responses (e.g. 200 + 400) silently collapsed into a single Response type — the inline-naming function in analysis.rs ignored its status_code argument so the second schema overwrote the first in the schema registry. The generated client then tried to deserialize success bodies into the error shape. Fix the naming collision and reshape the error story: - ApiError<E> envelope always carries status + headers + raw body, so callers can inspect what the server actually sent without hacking the generated code (the original side-tangent in #8). - ApiOpError<E> = Transport(HttpError) | Api(ApiError<E>) is the new return type for every generated operation method. - Per-operation error enums are emitted when an operation declares non-2xx body schemas; otherwise ApiOpError<serde_json::Value> falls back so the body is still inspectable as JSON. - Response handler reads the body to a string before any typed parse, so deserialization failures preserve the bytes. Breaking: generated method signatures change from HttpResult<T> to Result<T, ApiOpError<E>>. The HttpError::Http variant and from_status helper are removed from generated code (replaced by ApiError<E>). Bump to 0.2.0 per CLAUDE.md "no backwards compat" policy. End-to-end compile tests cover the toy multi-response spec plus the anthropic and openai fixtures so the new shape is exercised against realistic schemas, not just string-search assertions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
Closes #8. Operations that declared multiple responses (e.g. 200 + 400) silently collapsed into a single Response type — the inline-naming function in
analysis.rsignored itsstatus_codeargument so the second schema overwrote the first. The generated client then tried to deserialize success bodies into the error shape.This PR reshapes error handling around a typed envelope so the right thing happens whether the success/error bodies are declared, undeclared, or present-but-undeserializable. Discussion of the design is in issue #8.
Design
ApiError<E>— envelope that always carriesstatus + headers + raw body, with optionaltyped: Option<E>populated when the body matched a declared schema. Solves the side-tangent ask from Multiple Operation Responses Appear to Not Be Handled #8 (you can now inspect what the server actually sent without modifying the generated code).ApiOpError<E> = Transport(HttpError) | Api(ApiError<E>)— new return-type wrapper for every generated operation method.{Op}ApiError { Status400(BadRequestBody), … }; ops without declared error bodies fall back toApiOpError<serde_json::Value>so the body is still inspectable as JSON.The reasoning for not using a single all-responses enum is in the issue thread — short version: it forces every caller to match every variant and loses
Resultergonomics. Successes areOk(T), errors are typedErr(ApiOpError<E>).Breaking changes (0.2.0)
HttpResult<T>toResult<T, ApiOpError<E>>.HttpError::Http { ... }variant andfrom_statushelper are removed from generated code (replaced byApiError<E>). Theis_client_error/is_server_errorhelpers move toApiError<E>.0.2.0perCLAUDE.md"no backwards compat" policy.Test plan
cargo test— all suites green (unit + snapshot + integration)tests/multi_response_client_test.rs:Status400(BadRequest)variant, andApiOpError<…>signaturestest_multiple_inline_responses_have_distinct_schemas) — fails onmain, passes hereexamples/generated/client.rsregenerated and committed so the example output matches the new shape🤖 Generated with Claude Code