Skip to content

feat(ir)!: make ErrorCase a response: name, headers, media types - #438

Open
fuad-daoud wants to merge 1 commit into
stack/3-ir-fieldsfrom
stack/4-errorcase
Open

feat(ir)!: make ErrorCase a response: name, headers, media types#438
fuad-daoud wants to merge 1 commit into
stack/3-ir-fieldsfrom
stack/4-errorcase

Conversation

@fuad-daoud

Copy link
Copy Markdown
Collaborator

Closes #422. Breaking, and the largest change in the stack.

ir.ErrorCase and ir.Response were not siblings. Responses split by status — 2xx/3xx to Response, 4xx/5xx/default to ErrorCase — and the two nodes were shaped differently:

Response ErrorCase before
status spelling Name.Hint = "200" nothing — only a provenance pointer
headers Headers []Property absent, dumped to Unmodeled
content all media types one bare TypeRef, the rest dumped

So anything a consumer did with responses behaved differently by status class, silently. Retry-After and rate-limit headers live on 429 and 503 — precisely the side with no typed home. A 4xx declaring two media types kept one and lost the media-type key entirely.

703 occurrences of "error response media type has no ErrorCase home" across GitHub and Stripe.

ErrorCase now lowers through the same helpers as the success path — responseName, lowerHeaders, lowerPayload. Four functions that existed only because of the gap are deleted, and with them two info diagnostics and the openapi:headers / openapi:content writes.

pass/validate.go grows the fourth Payload carrier, reached at both positions an ErrorCase hangs from — Operation.Errors and Service.CommonErrors. The completeness guard TestEncodingCarriers_NameEveryPayloadFieldInTheIR fired on its own and named it.

The conformance fixture gained a 429 declaring two media types and two rate-limit headers, so the new fields are witnessed, not merely present — deleting either reddens the case.

BREAKING CHANGE: ErrorCase.type is removed; name, payload and headers take its place. A 0.3.0 consumer finds no type on an error case and cannot reach its models at all.


Stack 4 of 8. Base stack/3-ir-fields — review and merge bottom-up. Every commit here passed make gate when it landed, and the full gate was re-run on the top of the stack. Run it as GOTOOLCHAIN=go1.26.3 make gate; this machine's Go 1.27 fails it for reasons unrelated to any change (#431).

🤖 Generated with Claude Code

https://claude.ai/code/session_01SYeBgDsskwnyitLgPGCPn1

ir.ErrorCase and ir.Response are two lowerings of one Response Object, but
only one of them could say what the source declared. Response carries a
Name whose Hint is the status spelling, Headers, and a Payload holding
every media type; ErrorCase carried none of the three — no name at all, no
headers, and one bare TypeRef where the content map belongs.

Everything that fell outside those fields went to ErrorCase.Unmodeled with
an info diagnostic, so a consumer's behaviour changed with the status
class and nothing said so:

- Retry-After and the rate-limit family live on 429 and 503, precisely the
  side with no typed home for a header.
- A 4xx declaring application/json and application/problem+json kept the
  first schema and lost the media-type key entirely; a 4xx declaring one
  media type lost the key it was written under.
- "5XX" and "default" had no faithful round-trip: StatusRange renders
  {500,599} and {0,0} with no record of how the source spelled them.

ErrorCase now has Name Naming, Headers []Property and Payload *Payload in
place of Type, each spelled as Response spells it, and the error path
lowers through the same responseName, lowerHeaders and lowerPayload the
success path uses. preserveErrorHeaders, fillErrorType, preserveErrorContent
and errorContentMessage existed only to soften this gap and are gone with
it, along with the two info diagnostics they emitted.

pass.checkEncodingKeys grows a fourth Payload carrier, reached at both
positions an ErrorCase hangs from — an operation's Errors and a service's
CommonErrors — since a check walking only the first would resolve a
service-level error's encoding keys against nothing in silence.

BREAKING CHANGE: ErrorCase.Type is removed; an error case's models are its
Payload.Contents entries' types. The JSON gains "name", "payload" and
"headers" and loses "type". ir.IRVersion is deliberately not moved here:
per ir-design.md §2.1 a line of work bumps it once, where it lands on main,
and two earlier shape changes on this branch left it alone for the same
reason.

The normative rows in docs/ir-design.md that described the old behaviour
are updated, as is the error-taxonomy example in docs/emitter-design.md.
The per-status-errors conformance fixture gains a 429 declaring two media
types and two rate-limit headers, which is what makes the new fields
witnessed rather than merely present.

Closes #422

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYeBgDsskwnyitLgPGCPn1
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