Generate code for OpenAPI 3.2 query/additionalOperations/in:querystring

Draft
KH
6d ago
+9974-2924

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:

GeneratorLibrary
javaokhttp-gson (default)
go—
pythonurllib3
typescript-fetchfetch
rustreqwest
rubyhttpx
phpguzzle
csharpgenerichost
kotlinjvm-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.

  • additionalOperations keys are sent as wire HTTP method names verbatim
    (a new wireHttpMethod path bypasses the existing uppercase-normalization
    that's correct for the 8 fixed methods but wrong for arbitrary ones)
  • in: querystring becomes CodegenParameter.isQueryStringParam, typed as
    a plain string via the target language's own type mapping (content-derived
    model import/setContent is skipped for it)
  • Each language gates supportsAdditionalOperations() /
    supportsQueryStringParameters() per-library with a concrete reason —
    e.g. Python: aiohttp/httpx call method.upper() and would corrupt
    customMethod into CUSTOMMETHOD; 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 (default false, mirroring the
    existing supportsQueryStringParameters() 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
    required plus its allOf chain, 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's PathItem.HttpMethod
    enum gaining a QUERY value, which any code iterating that enum directly
    will now see, independent of anything in this PR.
  • MergedSpecBuilder (multi-spec merge) now includes additionalOperations
    in its merge and operation-ID conflict detection.
  • OpenApiEvaluator still assumes the fixed HttpMethod enum and doesn't
    support arbitrary additionalOperations.

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; kotlinc compile checks for
    okhttp4/vertx/ktor/multiplatform/spring generated clients, plus a live
    ServerSocket capture asserting form fields reach the wire.
  • C# (generichost): dotnet restore+run with a raw TcpListener capture 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

  • additionalOperations keys 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 like CHECK&FETCH survive Mustache's HTML escaping, and skips keys that aren't valid RFC 9110 tokens so they can't generate uncompilable string literals.
  • in: querystring parameters become CodegenParameter.isQueryStringParam and 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 (default false) 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 OpenAPI object, generation fails immediately with the parser's diagnostics instead of a generic downstream error.

Additional fixes

  • k6 skips 3.2 query operations with a warning instead of emitting http.query() calls, and no longer leaves empty request groups when a query-only path coexists with a sibling dataextract operation.
  • 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 named it no 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 additionalOperations entries independently, so a key like GET alongside the fixed get: field can no longer overwrite the fixed method's entry.