[dart] fix: explode object query parameters
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 | |
|---|---|
| expected | category=books&createdDate%3Agte=2023-01-01 |
| dart before | filter=%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
Objectand gets aMapcast; a declared map needs none (dart analyzeon the generated fixture client: no issues). - Each entry goes through
_queryParamswith'multi', after dropping null items and turning anyIterableinto aList, so the key repeats per element (tld=com&tld=net). - A null entry is left out, as
_queryParamsalready did. deepObjectandexplode: falseparameters produce the line they did before.
Verified
A client generated from the fixture, against a server that echoes its raw query string:
| input | on 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,isModelrather thanisMap) still go on the wire whole; iterating one would send the dart property names rather than the wire names. deepObjectandexplode: falsekeep their singleMap.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
TypeErrorat the cast instead of going out as itstoString(), 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'slanguageparameter (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