Generate code for OpenAPI 3.2 query/additionalOperations/in:querystring
Status: Draft — blocked on swagger-parser releasing OAS 3.2 support
This PR is not ready to merge. It builds on #24969 (already open, please
review that one first — it warns instead of silently dropping unrecognized
path-item operations like 3.2's query), and depends on parsing support
that only exists in a fork of swagger-parser
(swagger-api/swagger-parser#2402, itself a draft blocked on a swagger-core
fork). pom.xml currently points at a locally-installed swagger-parser
SNAPSHOT that isn't available to anyone else — this PR cannot build or pass
CI (the RequireReleaseDeps enforcer rule rejects it outright) until that
chain lands upstream.
I opened it as a draft anyway so the codegen work is visible and reviewable
in context. I'll drop the dependency on the forks and rebase once
swagger-core/swagger-parser cut releases with OAS 3.2 support.
What this adds (on top of #24969)
Generates code for the OAS 3.2 constructs that the swagger-parser fork now
parses into the model: the query HTTP method, additionalOperations
(arbitrary-named path-item operations), and in: querystring parameters.
Scope: the cross-language core path (so query/additionalOperations
are picked up structurally everywhere), plus nine generator/library
combinations wired up end-to-end and verified on the wire:
| Generator | Library |
|---|---|
| java | okhttp-gson (default) |
| go | — |
| python | urllib3 |
| typescript-fetch | fetch |
| rust | reqwest |
| ruby | httpx |
| php | guzzle |
| csharp | generichost |
| kotlin | jvm-okhttp4 |
Generators that have not opted in warn and skip 3.2 operations instead of
emitting non-compiling references to HTTP-method enum constants that don't
exist for their target language.
additionalOperationskeys are sent as wire HTTP method names verbatim
(a newwireHttpMethodpath bypasses the existing uppercase-normalization
that's correct for the 8 fixed methods but wrong for arbitrary ones)in: querystringbecomesCodegenParameter.isQueryStringParam, typed as
a plain string via the target language's own type mapping (content-derived
model import/setContentis skipped for it)- Each language gates
supportsAdditionalOperations()/
supportsQueryStringParameters()per-library with a concrete reason —
e.g. Python: aiohttp/httpx callmethod.upper()and would corrupt
customMethodintoCUSTOMMETHOD; Ruby: typhoeus crashes on
non-alphanumeric tokens and faraday rejects them; C#: restsharp's Method
enum rejects non-standard tokens; Kotlin: only jvm-okhttp4's shadow
request config can carry custom methods. - A
supportsAdditionalOperations()hook (defaultfalse, mirroring the
existingsupportsQueryStringParameters()hook) makes generators that
haven't opted in warn-and-skip rather than silently emit references to
HTTP-method enum constants (PURGE,QUERY, ...) that don't exist for
their target language — this was a real gap caught during review: the
shared pipeline originally let these values reach every language's
templates, and 15+ non-Java templates (Spring, Kotlin, TypeScript, ...)
would have produced non-compiling output for a 3.2 spec with no warning at
all.
Additional fixes in this branch (kept as separate commits)
- allOf form required flags (
bc20a2c12d3,7ab9da53d49): required
flags for form parameters are now collected only from the schema's own
requiredplus itsallOfchain, matched by baseName — oneOf/anyOf
branch properties no longer leak into required, fixing a regression where
properties+oneOf models made every form field required. - Generated-code identifier collisions (Kotlin, PHP): spec parameters
named like template locals (localVariable*), API members
(request,basePath,parseDateToQueryString), or implicit receivers
(it,value) no longer shadow generated code — names are renamed or
qualified while wire names stay unchanged. Includes a wire-level Vert.x
fix where collected form parameters were never actually sent. $in wire names (Kotlin): wire names containing$(e.g. OData
$filter) are escaped in generated Kotlin string literals across all
client libraries, matching jvm-okhttp's existing behavior.
Known limitations (disclosed, not blocking this draft)
- Only the nine generator/library combinations above emit 3.2 operations;
every other library (including the other Kotlin libraries, Java's
non-okhttp-gson libraries, and the non-fetch TypeScript generators)
warns and skips. - At least one generator (
k6) builds its supporting script from its own
independent traversal of the model rather than going through the shared,
now-gated pipeline, so it isn't covered by the new
supportsAdditionalOperations()guard. A handful of other generators use
a similar direct-traversal pattern and haven't been individually audited.
None of this is new — it follows from swagger-core'sPathItem.HttpMethod
enum gaining aQUERYvalue, which any code iterating that enum directly
will now see, independent of anything in this PR. MergedSpecBuilder(multi-spec merge) now includesadditionalOperations
in its merge and operation-ID conflict detection.OpenApiEvaluatorstill assumes the fixedHttpMethodenum and doesn't
support arbitraryadditionalOperations.
Testing
Core classes: DefaultGeneratorTest (27), DefaultCodegenTest (174+7),
JavaClientCodegenTest (287), InlineModelResolverTest (62),
OpenAPINormalizerTest (71) — 628 total, 0 failures.
Per-language verification goes beyond unit tests — generated clients were
compiled and exercised against raw socket captures to prove the wire
behavior (verbatim method casing, verbatim query-string appending, form
bodies actually transmitted):
- Kotlin package: 579 tests, 0 failures;
kotlinccompile checks for
okhttp4/vertx/ktor/multiplatform/spring generated clients, plus a live
ServerSocketcapture asserting form fields reach the wire. - C# (generichost): dotnet restore+run with a raw
TcpListenercapture of
QUERY/custom-method requests on both net8.0 and net10.0. - PHP (guzzle), Ruby (httpx), Rust (reqwest), Go, Python (urllib3),
typescript-fetch: raw TCP/socket captures of the actual HTTP request
lines and bodies.
Sample regeneration (./bin/generate-samples.sh, 124+ configs) confirmed
clean — diffs limited to the intended template changes.
🤖 Generated with Claude Code
Summary by cubic
Generates code for OpenAPI 3.2's query HTTP method, additionalOperations map, and in: querystring parameters, wired end-to-end for Java (okhttp-gson), Go, Python (urllib3), typescript-fetch, Rust (reqwest), Ruby (httpx), PHP (guzzle), C# (generichost), and Kotlin (jvm-okhttp4). Also fixes issue #24212: with --skip-validate-spec, unrecognized path-item operations are flagged with an explicit warning naming the dropped member instead of being silently dropped. The branch is merged with current master, but stays unmergeable: it pins swagger-parser 2.1.49-SNAPSHOT because the OAS 3.2 model/parser classes are not in any released version, so CI (including the RequireReleaseDeps enforcer rule) cannot pass until upstream cuts a release.
Behavior changes
additionalOperationskeys are sent verbatim as the wire HTTP method name; invalid RFC 9110 tokens, and case-variants some clients would normalize, are warned and skipped. Java (okhttp-gson) emits the method name unescaped so tokens likeCHECK&FETCHsurvive Mustache's HTML escaping, and skips keys that aren't valid RFC 9110 tokens so they can't generate uncompilable string literals.in: querystringparameters becomeCodegenParameter.isQueryStringParamand append their value verbatim to the request path, skipping content-derived model imports. Operations using them are skipped with a warning in generators without support instead of corrupting the already-encoded value via name=value serialization.- A new
supportsAdditionalOperations()hook (defaultfalse) makes unsupported generators warn-and-skip instead of emitting references to HTTP-method enum constants that don't exist for their target language. Unsupported generators no longer flatten inline schemas from callbacks attached to 3.2 operations. - When the parser produces no
OpenAPIobject, generation fails immediately with the parser's diagnostics instead of a generic downstream error.
Additional fixes
k6skips 3.2queryoperations with a warning instead of emittinghttp.query()calls, and no longer leaves empty request groups when a query-only path coexists with a siblingdataextractoperation.- Parameters and properties colliding with generated template locals or API members are renamed (wire names preserved) across Kotlin and PHP so generated clients compile and send the intended values. Kotlin also escapes
$in paths and wire names across all libraries, jvm-vertx multipart file params transmit file content instead of the local path, and jvm-ktor binds its form builder explicitly so a field nameditno longer shadows it. - Form-parameter required flags now match the schema property name, so allOf-inherited required fields are no longer dropped and oneOf/anyOf branches no longer force fields required.
- The dropped-operation warning for issue #24212 no longer false-positives on quoted segments nested inside path names or on dotted custom method names; verbatim query handling was hardened across Go (raw query ordering), Python (case-exact method dispatch), and typescript-fetch (fragment-aware
?detection, warning on fetch-forbidden verbs). Rust escapes markdown specials in generated method-name docs. - The FILTER normalizer evaluates
additionalOperationsentries independently, so a key likeGETalongside the fixedget:field can no longer overwrite the fixed method's entry.