[dart] fix: explode object query parameters

Merged
WI
1mo ago
+95-1

Bug

A query parameter whose schema is an object, with style/explode left at their defaults (form, true), must go on the wire as one parameter per entry, keyed by the property name alone. The dart client stringified the whole map with Map.toString() into a single parameter instead.

parameters:
  - in: query
    name: filter
    schema:
      type: object

called with {"category": "books", "createdDate:gte": "2023-01-01"}:

on the wire
expectedcategory=books&createdDate%3Agte=2023-01-01
dart beforefilter=%7Bcategory%3A+books%2C+createdDate%3Agte%3A+2023-01-01%7D

Series

One of six per-language PRs for the same bug: #24797 go, #24802 python, #24803 typescript-fetch, #24867 kotlin, #24868 dart, #24869 ruby. No shared main/ code; each adds the same fixture, 3_0/exploded-object-query-param.yaml.

Fix

dart2/api.mustache handed every query parameter whole to _queryParams, which knows only a collection format and falls through to toString(). Like the python port (#24802), the generated api now iterates an exploded map entry by entry at the call site; _queryParams itself is untouched.

  • A free-form object is typed Object and gets a Map cast; a declared map needs none (dart analyze on the generated fixture client: no issues).
  • Each entry goes through _queryParams with 'multi', after dropping null items and turning any Iterable into a List, so the key repeats per element (tld=com&tld=net).
  • A null entry is left out, as _queryParams already did.
  • deepObject and explode: false parameters produce the line they did before.

Verified

A client generated from the fixture, against a server that echoes its raw query string:

inputon the wire
filter or typedFilter with {"category": "books", "createdDate:gte": "2023-01-01"}category=books&createdDate%3Agte=2023-01-01
filter with {"anyList": ["x", null, 2]}anyList=x&anyList=2
declared page=declared plus filter with {"page": "fromMap"}page=declared&page=fromMap (both kept)
deepFilter (deepObject) or flatFilter (explode: false)Map.toString(), as on master (see Known gaps)

DartClientCodegenTest#testExplodedObjectQueryParameter fails without the fix.

Known gaps

  • Declared object models ($ref, isModel rather than isMap) still go on the wire whole; iterating one would send the dart property names rather than the wire names.
  • deepObject and explode: false keep their single Map.toString() parameter, as on master. Both are wrong per the spec (deepFilter[category]=books, flatFilter=category,books,…).
  • Nested objects inside an exploded map go out as their toString(); form style defines no encoding for them.
  • A non-map value in a free-form object parameter now throws a TypeError at the cast instead of going out as its toString(), as python raises for a non-dict.

PR checklist

  • Read the contribution guidelines.
  • Built the project and updated samples (./bin/generate-samples.sh bin/configs/dart*.yaml): 1 file, petstore_client_lib_fake/lib/api/fake_api.dart, from the petstore fixture's language parameter (a declared map with the default style). The dart-dio samples have their own templates and are untouched.
  • Technical committee: @yissachar @joernahrens @swipesight @jaumard

Generated with Claude Code