feat(generator): add xml response_format for XML-backed APIs#3065
feat(generator): add xml response_format for XML-backed APIs#3065coopdogGGs wants to merge 6 commits into
Conversation
Add ResponseFormatXML, Endpoint.UsesXMLResponse, APISpec.HasXMLResponse, and accept xml in response_format validation. Mirrors the existing csv/html/binary formats.
Detect XML-only success responses and set response_format: xml plus an Accept: application/xml override so the server returns XML rather than 406-ing the default application/json. Mixed JSON+XML responses keep the JSON default.
Emit a cliutil.XMLToJSON helper (stdlib encoding/xml, BadgerFish-lite: @attr, #text, repeated->array, graceful raw passthrough on decode failure) when any endpoint uses response_format: xml. The generated client runs XML-only success bodies through it so --json, --select, table output, and MCP tools work unchanged. Helper and client branch are gated on HasXMLResponse so JSON-only CLIs are byte-identical.
Merge Protections🔴 2 of 2 protections blocking · waiting on 🤖 CI and 🙋 you
🔴 require-ready-label-and-ciWaiting for
This rule is failing.
🔴 🚦 Auto-queueWaiting for
This rule is failing.When all merge protections are satisfied and these conditions match, this pull request will be queued automatically.
|
Greptile SummaryThis PR adds
Confidence Score: 4/5Safe to merge for pure XML specs (the motivating BGG case); mixed XML+JSON specs would send the wrong Accept header on JSON endpoints. The global Accept fallback is changed from internal/generator/templates/client.go.tmpl — both the Accept fallback and the XML normalization branch are gated at the spec level rather than the endpoint level. Important Files Changed
|
…ormat XML-backed specs now negotiate application/xml on the if-empty Accept default instead of application/json, so direct client calls on XML-only APIs avoid a 406. Non-XML specs are byte-identical (still application/json).
|
@coopdogGGs Your agents needs to follow directions in AGENTS.md if it's going to open PRs. Specifically, it needs to babysit the PRs to address code review feedback. In addition to addressing code review feedback, please fix the Greptile comment:
When you've done your fixes, feel free to mark as ready for review |
The xml response_format change added three explanatory comment lines to the if-empty Accept default in client.go.tmpl, which render into every generated client. Regenerate the five affected golden client.go fixtures so the golden/full-golden lanes pass.
Greptile flagged that gating XML behavior on HasXMLResponse (spec has at least one XML endpoint) is too broad for a mixed spec: a JSON endpoint in the same spec would receive the application/xml Accept default and have any xml-content-type response run through XMLToJSON, risking a 406 or a wrong normalization. Add APISpec.AllResponsesXML (true only when every endpoint is response_format: xml) and gate the spec-wide XML behavior on it instead: the application/xml Accept default, the XML to JSON normalization call site, the isXMLResponseContentType helper, and xml_parse.go emission. XML endpoints in a mixed spec still get their per-endpoint Accept override from the parser; only the spec-wide fallback is withheld, so JSON endpoints keep application/json and are never XML-normalized. BoardGameGeek and any other all-XML API are unaffected (every endpoint is XML, so AllResponsesXML is true). Existing golden cases are all non-XML, so both gates evaluate false and output is byte-identical. Adds spec unit tests for AllResponsesXML and a generator test that a mixed XML/JSON spec emits no spec-wide XML code.
Review follow-up (commit b87db4b)Thanks for the thorough pass. Addressed the two P1 findings; the two P2 findings are deliberate scope decisions explained below. ✅ P1 — Global Accept override breaks JSON endpoints in mixed specs (
|
|
@coopdogGGs still ned this fixed:
|
Intent
Generated CLIs/MCP servers could not cleanly consume APIs that return XML. The
HTTP layer already let XML bytes through as text, but with the default
response_format: jsonthe body was opaque to--json,--select, tableoutput, and MCP tools. This adds first-class XML support so an XML API behaves
like any JSON API in a printed CLI.
Issue: N/A
Approach
Add
xmlas a fifthresponse_format, mirroring howcsvalready turns anon-JSON body into a generic JSON-compatible structure inside the generated
client. The decision was XML→JSON normalization (Design A) over an
xml_extractselector contract mirroringhtml_extract(Design B): A matchesthe CSV precedent, needs no per-mode DOM/XPath machinery or profiler
propagation, and is enough for the motivating consumer (read-only XML APIs).
Three layers:
ResponseFormatXML,Endpoint.UsesXMLResponse,APISpec.HasXMLResponse, and validation acceptsxml.response_format: xmlplus anAccept: application/xmloverride so theserver returns XML instead of 406-ing the default
application/json. MixedJSON+XML responses keep the JSON default.
response_format: xml, emit acliutil.XMLToJSONhelper (stdlibencoding/xml, BadgerFish-lite:@attr,#text, repeated siblings → array, graceful raw pass-through on decodefailure). The client runs XML-only success bodies through it before
returning, so every downstream consumer sees ordinary JSON.
The helper, the client branch, and the
isXMLResponseContentTypehelper areall gated on
HasXMLResponse, so JSON-only CLIs are byte-identical to before.Scope
Primary area:
internal/spec,internal/openapi,internal/generator(templates + emission).
Why this belongs in this repo: this is core Printing Press generation behavior
— a new response-format class that any future XML-backed spec can use — not a
fix to one printed CLI.
Catalog Justification
N/A — no
catalog/*.yamlorcatalog/specs/**changes. (Motivated byBoardGameGeek's XML API, but no catalog entry is added here; that remains a
separate follow-up gated on BGG application approval.)
Risk
Low and contained. All new generation is gated on
HasXMLResponse, which isfalse for every existing spec, so JSON/CSV/HTML/binary CLIs are unaffected. The
new parser branch only fires when a success response is XML-only (never when
JSON is also offered), so it cannot reroute existing JSON endpoints. Worst case
for a future XML API is a decode failure, which returns the raw XML bytes
unchanged rather than corrupting the body.
Output Contract
Generated output changes only for specs that declare/auto-detect
response_format: xml:internal/cliutil/xml_parse.go(gated onHasXMLResponse).isXMLResponseContentTypehelper and a normalization branch in theclient's success path (both gated).
Evidence:
TestGenerateXMLResponseParseHelpergenerates an XML-API CLI, then compiles and runs the generated
cliutil.XMLToJSONagainst table cases (attributes, nesting, repeatedsiblings → array, mixed attr+text, malformed pass-through).
TestGenerateJSONOnlyOmitsXMLResponseParseHelperassertsJSON-only CLIs emit no
xml_parse.go.generate-golden-apifixture; all 39generated
.gofiles are byte-identical to the committed golden expectedoutput (only the copyright-owner line differs, an author-handle artifact).
Verification
go build ./...— passgo vet ./internal/spec/... ./internal/openapi/... ./internal/generator/...— passgofmt -lon touched files — cleango test ./internal/spec/...— passgo test ./internal/openapi/...— pass except the pre-existingWindows-only failure
TestGenerateDataEnvelopeAllOfBodyFlags(builds thebinary without the
.exesuffix atparser_test.go:1411); passes whenskipped. Confirmed it fails identically on
main.go test ./internal/generator/... -run XMLResponse— pass (compiles + runsgenerated helper)
scripts/golden.sh verify— Not conclusive locally: on the author's Windowsbox it reports 31 changed cases, but
mainreports the same 31 (CRLFline-ending normalization + local Go import ordering), so the baseline itself
does not match here. None of the diffs are XML-related. The per-file
byte-identity check above isolates this change's zero drift; CI (Linux) runs
the authoritative golden verify.
Generator/template change: verified generated output, including emitted-code assertions or compiled generated CLI output
Generator/template change: covered the affected fallback or variant shape, not only happy-path fixtures
Generator/template change: checked emitted definitions and call sites for matching gates
AI / Automation Disclosure