Skip to content

feat(client): typed query parameter serialization (form/deepObject styles) (#27)#28

Merged
lightsofapollo merged 7 commits into
mainfrom
issue-27-exploded-query-params
Jul 13, 2026
Merged

feat(client): typed query parameter serialization (form/deepObject styles) (#27)#28
lightsofapollo merged 7 commits into
mainfrom
issue-27-exploded-query-params

Conversation

@lightsofapollo

@lightsofapollo lightsofapollo commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #27, and completes the client half of T14 (beads openapi-generator-anu).

Query parameters with object or array schemas previously mapped to Option<impl AsRef<str>> and went out as a single opaque name=<string> pair — wrong on the wire for every OAS style. Now the analyzer assigns each such parameter a QuerySerialization from its style/explode + schema shape, and the client generates the matching wire format:

Parameter shape New argument Wire format
object, form + explode=true (OAS defaults) Option<Struct> ?color=red&size=5
object, form + explode=false Option<Struct> ?filter=color,red,size,5
object, deepObject Option<Struct> ?filter[color]=red
array, form + explode=true (OAS defaults) Option<Vec<T>> ?tags=a&tags=b
array, form + explode=false Option<Vec<T>> ?tags=a,b,c

Struct is the referenced component model for $ref schemas or a synthesized {Operation}{Param} struct for inline objects. T maps scalars through the TypeMapper or resolves $ref items to generated string enums (which emit Display); the ArrayItemType enum keeps scalar type strings and raw schema names distinct so enum names sanitize through to_rust_type_name (caught live: cloudflare's resource-sharing_resource_type).

Deliberately unchanged (still the opaque string passthrough): deepObject arrays (undefined in OAS — stripe's expand[]), spaceDelimited/pipeDelimited, arrays of objects, and server-side extraction (openapi-generator-0jz).

This is a breaking change by design — regenerated clients get new signatures replacing silently-wrong wire output. README now carries a pre-1.0 breaking-changes log, and this PR bumps the crate version to 0.6.0.

Also included: scripts/spec-compile.sh now checks all scratch crates as one cargo workspace against a shared, run-surviving CARGO_TARGET_DIR — the full affected-spec sweep dropped from ~2-4 min/spec to a 29-spec pass in minutes.

Verification

  • 15 regression tests in tests/exploded_query_params_test.rs covering every serialization mode, fallbacks, required/optional, coexistence — plus runtime pins proving reqwest emits ?color=red&size=5, no dangling ? for all-None structs, and percent-encoded deepObject brackets.
  • Full test suite green; zero snapshot changes.
  • All 32 bundled real-world specs whose parameters flip under the new rules generate and cargo check cleanly (incl. cloudflare 2458 exploded arrays, microsoft-graph 13k comma-joined arrays, stripe/telnyx/modern-treasury deepObject filters).
  • Hand-inspected generated Modern Treasury output: metadata[key]=value deepObject filters match their documented wire format.

🤖 Generated with Claude Code

lightsofapollo and others added 5 commits July 12, 2026 17:33
Query params with object schemas and form-explode semantics (the OAS 3.x
defaults for in:query) previously mapped to Option<impl AsRef<str>> and
were sent as one opaque filter=<string> pair. Per RFC 6570 form-explosion
each object property must be its own query pair (?color=red&size=big).

- analyzer: type such params as a struct — synthesized (e.g.
  FindWidgetsFilter) for inline object schemas, resolved for $refs —
  and mark them form_explode_object
- client: emit req.query(&value) for marked params; reqwest serializes
  the struct via serde_urlencoded, one pair per set property, None
  fields omitted
- deepObject and form+explode=false keep the string fallback (tracked
  in openapi-generator-anu); server codegen unchanged (openapi-generator-0jz)

Closes #27

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Completes T14 for the client (openapi-generator-anu), building on the
form-exploded object support from GH #27:

- QuerySerialization enum on ParameterInfo replaces the
  form_explode_object bool; the analyzer assigns it from style/explode +
  schema shape per OAS 3.x defaults
- form + explode=false objects: struct arg, one comma-joined
  ?filter=k1,v1,k2,v2 pair
- deepObject objects: struct arg, bracketed ?filter[color]=red pairs
  (percent-encoded by reqwest; runtime-pinned in tests)
- form arrays: Vec<T> args — explode=true repeats ?tags=a&tags=b,
  explode=false joins ?tags=a,b,c, empty vecs omitted; T maps scalars
  through the TypeMapper and $ref items to generated string enums
  (which emit Display)
- still on the string fallback: deepObject arrays (undefined in OAS;
  stripe expand[]), space/pipeDelimited, arrays of objects
- README gains a pre-1.0 breaking-changes log (user request): these are
  signature-level breaks on regeneration, replacing silently-wrong wire
  output; ship as 0.6.0

Server codegen and registry still see rust_type=String for all of these
(openapi-generator-0jz).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every scratch crate has an identical dependency tree, but each got its
own target/, so reqwest/chrono/hyper/serde recompiled from scratch for
all ~55 specs (~2-4 min each) — dwarfing the seconds it takes to check
the generated code itself. Route the cargo-check step through a shared
SPEC_COMPILE_TARGET_DIR (default tmp/spec-compile-target) that lives
outside the per-run rm -rf: deps compile once ever, later runs and later
specs start warm. The generator build keeps the workspace target/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces 55 sequential cargo-check invocations (each re-resolving deps
and taking the target-dir lock alone) with a single workspace check:
one resolution, one lock, --keep-going, and cargo parallelizes the
per-crate checks across cores itself. Per-spec attribution happens via
cheap cached `-p` re-checks only when the workspace pass fails.
Empirical: 3-spec run = 10s wall including dep bootstrap into the
shared target dir (previously minutes per spec).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lightsofapollo lightsofapollo changed the title feat(client): form-exploded object query parameters (#27) feat(client): typed query parameter serialization (form/deepObject styles) (#27) Jul 13, 2026
lightsofapollo and others added 2 commits July 12, 2026 18:46
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0.6.0 releases the typed query parameter serialization from #27/#28 —
a breaking change for regenerated clients, logged in the README's
pre-1.0 breaking-changes section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lightsofapollo lightsofapollo marked this pull request as ready for review July 13, 2026 01:03
@lightsofapollo lightsofapollo merged commit 833185d into main Jul 13, 2026
5 checks passed
lightsofapollo added a commit that referenced this pull request Jul 13, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for exploded query parameters

1 participant