Skip to content

Commit 4c7f308

Browse files
fredbiclaude
andcommitted
docs(media-types): expand client-inbound response section
The client-inbound section was 7 lines covering only the high-level "no Accept negotiation, dispatch by Content-Type". Expanded to cover the parts a reader actually needs when chasing a bug: - the pipeline from response.Header through resolveConsumer to the codegen-emitted operation Reader, with a small diagram; - what the operation Reader actually does (status code dispatch, consumer pick, body decode into the typed response struct); - how resolveConsumer picks a consumer — the four mediatype.Lookup tiers plus the "*/*" wildcard fallback; - where Runtime.MatchSuffix lands (it's resolveConsumer's opt-in surface), and how that interacts with the existing wildcard fallback; - the always-on alias bridge in this path: a server response with Content-Type: text/yaml decodes via a consumer registered at application/x-yaml or application/yaml regardless; - the three failure modes — malformed Content-Type, no consumer + no wildcard, silent wildcard fallback through "*/*". Refs the layered work that landed on fix/140-json-dialects. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
1 parent 1e8c4e1 commit 4c7f308

1 file changed

Lines changed: 95 additions & 5 deletions

File tree

docs/MEDIA_TYPES.md

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -470,11 +470,101 @@ preference fix from #286 are preserved verbatim.
470470

471471
## Client side — inbound responses
472472

473-
The client uses the operation's `Reader` plus the per-MIME `Consumers`
474-
map. There is no `Accept` negotiation step on the client beyond the
475-
header value the user (or codegen) sets on the request — the response
476-
content type is taken from `Content-Type` on the response and dispatched
477-
to the matching consumer.
473+
There is no `Accept` negotiation step at decode time. The client sent
474+
its `Accept` header on the request and is now reading whatever the
475+
server chose to return — the response's `Content-Type` header is the
476+
single input the codec dispatcher consults.
477+
478+
### Pipeline
479+
480+
```
481+
response.Header["Content-Type"]
482+
483+
484+
resolveConsumer(ct) ── client/runtime.go
485+
486+
▼ picks a runtime.Consumer
487+
operation.Reader ── codegen-emitted; switches on status code,
488+
│ hands the body to the picked consumer,
489+
▼ decodes into the typed response struct
490+
typed response value or error
491+
```
492+
493+
The codegen-emitted **operation `Reader`** is the piece most users
494+
never see. It's a generated function per operation that:
495+
496+
1. Reads the HTTP status code and selects the matching response
497+
definition from the spec.
498+
2. Calls `runtime.ContentType(response.Header)` to extract the bare
499+
mime.
500+
3. Invokes the runtime to resolve a consumer for that mime
501+
(`resolveConsumer`).
502+
4. Decodes the body into the response definition's Go type via
503+
`consumer.Consume(body, target)`.
504+
505+
If you are writing a custom client without codegen, you implement
506+
this function yourself.
507+
508+
### `resolveConsumer` — picking a consumer
509+
510+
`resolveConsumer(ct string)` in `client/runtime.go` is the single
511+
codec-lookup site on the client. It runs:
512+
513+
1. Parse `ct` (rejects malformed values with a `"parse content type:
514+
…"` error — surfaced as a client-side error, not as a server
515+
response).
516+
2. `mediatype.Lookup(r.Consumers, ct, r.matchOpts()...)` — runs the
517+
four always-on tiers (raw key, parsed canonical, alias query-side,
518+
alias map-side) plus the opt-in suffix tier when
519+
`Runtime.MatchSuffix` is set. See "Beyond strict matching" above.
520+
3. On lookup miss, fall back to `r.Consumers["*/*"]` if a wildcard
521+
consumer is registered.
522+
4. On full miss, return `"no consumer: %q"` — the operation `Reader`
523+
propagates this as the operation's error.
524+
525+
### Where `Runtime.MatchSuffix` lands
526+
527+
Setting `rt.MatchSuffix = true` flips the inbound decode path to
528+
tolerate RFC 6839 suffix media types: a response with
529+
`Content-Type: application/problem+json` finds the JSON consumer
530+
registered at `application/json`, decoded into whatever Go type the
531+
response definition declares. The wildcard `"*/*"` fallback runs
532+
unchanged after the suffix tier.
533+
534+
Symmetric to the server-side `Context.SetMatchSuffix(true)` — the
535+
opt-in is independent on each side and exists for exactly the same
536+
reason: real servers (or real clients) that don't strictly abide by
537+
the spec's `produces` / `consumes` declarations.
538+
539+
### Alias bridge — also active here
540+
541+
The always-on alias bridge applies on this path too. A client that
542+
registers the YAML consumer at the legacy `application/x-yaml` key
543+
(or, for that matter, leaves the default-map flip in place at
544+
`application/yaml`) handles a server response with
545+
`Content-Type: text/yaml` correctly — `mediatype.Lookup`
546+
canonicalizes both keys to `application/yaml` and finds the consumer
547+
regardless of which form was registered.
548+
549+
### Failure modes worth knowing
550+
551+
- **Malformed `Content-Type`** (e.g. trailing garbage, unterminated
552+
quoted string) — `resolveConsumer` returns an error sourced from
553+
`mime.ParseMediaType`, prefixed with `parse content type:`. The
554+
operation `Reader` surfaces this as the operation's error; no
555+
decode is attempted.
556+
- **No consumer, no wildcard registered**`"no consumer: %q"` with
557+
the offending Content-Type. Most commonly hit when the server
558+
returns an undeclared error mime (`application/problem+json` is the
559+
canonical example) and `Runtime.MatchSuffix` is off and `"*/*"` is
560+
not registered.
561+
- **Silent wildcard fallback** — if `Consumers["*/*"]` is registered
562+
(the default-map registers `runtime.ByteStreamConsumer` there), any
563+
unrecognised `Content-Type` decodes through that consumer. For a
564+
typed response struct, this usually fails inside the consumer's own
565+
unmarshal with a less specific error than the no-consumer case.
566+
Worth knowing if the runtime appears to "silently succeed at
567+
decoding garbage."
478568

479569
## `Accept-Encoding`
480570

0 commit comments

Comments
 (0)