Skip to content

fix: emit case clauses for bodyless responses to prevent default catch-all unmarshal error - #2427

Merged
mromaszewicz merged 3 commits into
oapi-codegen:mainfrom
lwc:fix/bodyless-response-default-catch-all
Jul 9, 2026
Merged

mromaszewicz merged 3 commits into
oapi-codegen:mainfrom
lwc:fix/bodyless-response-default-catch-all

Conversation

@lwc

@lwc lwc commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

When a spec defines a response without content (e.g. 204 No Content) alongside a default error response with JSON content, the generated Parse*Response function would attempt to json.Unmarshal an empty body because the default catch-all case (&& true) matched any status code.

Bodyless responses produce no type definitions, so the existing loop never visited them and no guarding case clause was emitted.

This adds a second pass over all declared responses that emits explicit break clauses for any response without content, placed before the default catch-all in the generated switch statement.

Fixes #2426

@lwc
lwc requested a review from a team as a code owner June 23, 2026 04:14
@greptile-apps

greptile-apps Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR updates generated response parsing for bodyless OpenAPI responses. The main changes are:

  • Adds explicit no-content switch cases for declared responses without content.
  • Orders bodyless exact and range response cases around explicit content parsers and default catch-alls.
  • Adds regression coverage for 204, 2XX, and multi-JSON default response cases.
  • Regenerates affected client fixtures with the new response parsing order.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
pkg/codegen/template_helpers.go Updates response unmarshal case generation to include bodyless responses and preserve parser precedence.
pkg/codegen/codegen_test.go Adds regression checks for bodyless responses with default catch-all parsing.
pkg/codegen/test_specs/bodyless-response-default.yaml Adds test operations covering exact no-content responses, range no-content responses, and strict JSON defaults.

Reviews (3): Last reviewed commit: "fix: sort response case clauses by respo..." | Re-trigger Greptile

Comment thread pkg/codegen/template_helpers.go Outdated
@lwc
lwc force-pushed the fix/bodyless-response-default-catch-all branch 2 times, most recently from 245e50e to 158e810 Compare June 23, 2026 04:27
Comment thread pkg/codegen/template_helpers.go Outdated
@lwc
lwc force-pushed the fix/bodyless-response-default-catch-all branch 2 times, most recently from 538bb04 to 158e810 Compare June 23, 2026 04:48
…h-all unmarshal error

When a spec defines a response without content (e.g. 204 No Content)
alongside a default error response with JSON content, the generated
Parse*Response function would attempt to json.Unmarshal an empty body
because the default catch-all case (&& true) matched any status code.

Bodyless responses produce no type definitions, so the existing loop
never visited them and no guarding case clause was emitted.

This adds a second pass over all declared responses that emits explicit
break clauses for any response without content. Sort key selection
ensures correct precedence:

- Exact status codes (e.g. 204) sort before content-handling cases,
  safe because they can only match their own status code.
- Range wildcards (e.g. 2XX) sort alongside content-handling cases,
  preventing a bodyless range from shadowing an explicit response
  (e.g. 200) that does have content.
- default is skipped (a bodyless default is degenerate).
@lwc
lwc force-pushed the fix/bodyless-response-default-catch-all branch from 158e810 to e65caa4 Compare June 23, 2026 05:03
@mromaszewicz mromaszewicz added the bug Something isn't working label Jul 9, 2026
# Conflicts:
#	pkg/codegen/codegen_test.go
…ing is robust

The previous sort keys ordered case clauses by content type first
("9.json.200", "9.xml.200"), which required the "B_nocontent" string
hack to slot bodyless range guards between explicit cases and the
default catch-all. That hack left two holes:

- A bodyless range guard (e.g. "2XX") keyed "9.json.B_nocontent_2XX"
  sorted before explicit XML/YAML cases ("9.xml.200", "9.yaml.200"),
  shadowing them: a 200 XML response would hit the 2XX break and
  return with XML200 unset.
- When a "default" response declares multiple JSON content types, its
  strict-match keys ("9.application/json.default") sorted before the
  range guard, so a bodyless 2XX response carrying a JSON Content-Type
  header would still hit the default unmarshal and fail on EOF.

Putting the response name first ("9.200.xml", "9.2XX.nocontent",
"9.default.application/json") makes plain lexicographic order yield
most-specific-first naturally: exact codes sort numerically, range
wildcards sort after the exact codes they cover ('X' > '9') and before
the next tier, and "default" sorts last ('d' > 'X'). No special-casing
needed.

Regenerated files pick up both the reordering and the no-content break
clauses from the previous commit, which had not been regenerated.
@mromaszewicz
mromaszewicz merged commit 29e2216 into oapi-codegen:main Jul 9, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generated response parser fails with unmarshal error when bodyless response matches a default catch-all

2 participants