Skip to content

fix: emit repeated query params for all multi-value streaming options#82

Merged
dg-coreylweathers merged 1 commit into
mainfrom
fix/streaming-multivalue-query-params
Jul 24, 2026
Merged

fix: emit repeated query params for all multi-value streaming options#82
dg-coreylweathers merged 1 commit into
mainfrom
fix/streaming-multivalue-query-params

Conversation

@GregHolmes

@GregHolmes GregHolmes commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Generalizes #81. That PR fixed multi-value keyterm serialization on the streaming connect path, but the same bug affects every array-valued query param. The generated streaming clients serialize each param by stringifying the value, so a multi-value List<String> collapses into a single param instead of repeated params:

  • Wrong: keyterm=%5Ba%2C+b%5D — one param, value "[a, b]"
  • Right: keyterm=a&keyterm=b — one param per value

The server treats the stringified list as a single nonsense value. The prerecorded REST path is unaffected — it already routes query params through the shared array-aware serializer with repeats enabled.

Root cause

Not a spec problem. The same Fern generator produces two query-building paths from the same spec. The REST path uses the shared array-aware serializer and is correct. The streaming/WebSocket path is hand-rolled and stringifies lists, so it mangles them. This is a generator defect in the WebSocket client template.

Fix

Route every array-valued streaming param through the same shared serializer the REST path uses, with array-as-repeats enabled, and drop PR #81's bespoke per-term loop. Params fixed:

  • v1 on /v1/listen: keyterm, keywords, replace, search, tag, extra
  • v2 on /v2/listen: keyterm, tag, language_hint

language_hint on v2 Flux — a String | List<String> union structurally identical to keyterm — had the identical bug and is included here.

Behavior

  • Single-string values: unchanged.
  • Multi-value lists: now correct as repeated params.
  • Empty list: now omits the param instead of emitting an empty stringified list.

Freeze / regen

Both streaming clients are already frozen in .fernignore; comments broadened from "multi-keyterm" to "multi-value query param". The durable fix belongs upstream in the Fern generator's WebSocket template, tracked so these entries can be unfrozen once it lands.

Tests

Extended connect-handshake wire coverage with MockWebServer, asserting the captured upgrade URL for every array param on both clients, plus scalar-string guards. ./gradlew spotlessJavaCheck test is green. Also verified with a throwaway matrix across 9 params and 4 shapes — multi-list, single-list, scalar, empty — plus exact raw-query and percent-encoding assertions, all passing.

Closes #77

Generalizes #81, which fixed multi-value keyterm serialization on the
streaming connect path. The same bug affects every array-valued query
param: the generated streaming clients stringify the value, so a
multi-value list collapses into a single param instead of repeated
params. The server treats the stringified list as one nonsense value.
The prerecorded REST path is unaffected.

Route every array-valued streaming param through the shared array-aware
serializer the REST path already uses, with repeats enabled, and drop
PR #81's bespoke per-term loop. Params fixed on v1 -- keyterm, keywords,
replace, search, tag, extra. Params fixed on v2 -- keyterm, tag,
language_hint.

language_hint on v2 Flux, a string-or-list union structurally identical
to keyterm, had the identical bug and is included here. Scalar strings
are unchanged; an empty list now omits the param.

Both streaming clients are already frozen in .fernignore; comments
broadened to cover all multi-value query params. The durable fix belongs
upstream in the Fern generator WebSocket template.

Closes #77
@GregHolmes
GregHolmes force-pushed the fix/streaming-multivalue-query-params branch from ab91c3f to 7fb57c0 Compare July 23, 2026 14:45

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified: serializer behavior, full multi-value param coverage on both listen clients, green build in Docker. LGTM. One non-blocking follow-up on speak v2 tag noted separately.

@dg-coreylweathers
dg-coreylweathers merged commit 746be15 into main Jul 24, 2026
8 checks passed
GregHolmes added a commit that referenced this pull request Jul 24, 2026
#85)

## Summary

Follow-up to #82, extending the same fix to TTS. The speak v2 streaming
client mangles a multi-value `tag` exactly as the listen clients did
before #82: it serializes the tag union by stringifying the value, so a
multi-value list collapses into a single param instead of repeated
params:

- Wrong: `tag=%5Ba%2C+b%5D` — one param, value "[a, b]"
- Right: `tag=a&tag=b` — one param per value

## Root cause

Same class as the listen fix. The Fern generator hand-rolls the
streaming query-building path and stringifies array params, while the
REST path routes through the shared array-aware serializer and is
correct. This is the same generator defect in the WebSocket client
template.

## Fix

Route `tag` through the shared array-aware serializer with repeats
enabled, matching the listen fix and the REST path. `tag` is the only
array-valued query param on speak v2. Scalar strings are unchanged; an
empty list now omits the param.

## Freeze / regen

The speak v2 client is already frozen in `.fernignore`; comments updated
so both the dispatcher patch and this multi-value fix are documented
against it. The durable fix belongs upstream in the Fern generator
WebSocket template, so listen and speak get cured together.

## Tests

New connect-handshake wire coverage with MockWebServer, asserting the
captured `/v2/speak` upgrade URL: a multi-value list becomes repeated
params, a single string stays one param. Also verified with a throwaway
matrix across four shapes -- multi-list, single-list, scalar, empty --
all passing. `./gradlew spotlessJavaCheck test` is green.

Credit to Corey for spotting this in the #82 review.
GregHolmes pushed a commit that referenced this pull request Jul 24, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.7.1](v0.7.0...v0.7.1)
(2026-07-24)


### Bug Fixes

* emit additionalProperties as query params on streaming connect
([#86](#86))
([28229fc](28229fc))
* emit repeated query params for all multi-value streaming options
([#82](#82))
([746be15](746be15)),
closes [#77](#77)
* emit repeated tag query params for multi-value speak v2 streaming
([#85](#85))
([e748619](e748619))

#### What's in this release

Three related fixes to streaming (WebSocket) query-parameter
serialization on `listen` and `speak`. All are bug fixes — no API
changes.

**Multi-keyterm streaming fix**
- Streaming connections mis-serialized a multi-value `keyterm` (and
other array-valued params): a `List<String>` was stringified into a
single param (`keyterm=[a, b]`) instead of repeated params
(`keyterm=a&keyterm=b`). The server treats the stringified list as one
nonsense term, so multiple key terms were not boosted — recognition of
them was actually degraded.
- Now fixed for every array-valued streaming query param — `listen`:
`keyterm`, `keywords`, `replace`, `search`, `tag`, `extra`,
`language_hint`; `speak`: `tag` — by routing them through the same
repeated-param serialization the REST path already uses (#81, #82, #85;
closes #77).
- **If you worked around this by hand-joining terms into a single
string, drop that workaround** — pass a real `List<String>` (e.g.
`ListenV1Keyterm.of(List.of("a", "b"))`) and each term is now sent as
its own `keyterm=` param.

**Unmodeled streaming query params now work (e.g. `no_delay`)**
- The streaming `connect(...)` builders dropped `additionalProperties`,
so unmodeled params set via `.additionalProperty("no_delay", true)`
never reached the URL — it was impossible to set `no_delay` on a
streaming connection.
- Now fixed on all four connect clients (`listen` v1/v2, `speak` v1/v2):
`additionalProperties` are emitted as query params, so any
not-yet-modeled param can be passed through until it gains a typed
option (#86; closes #83).

_All four streaming clients remain frozen in `.fernignore`; the durable
fix belongs in the Fern generator's WebSocket template, tracked for the
next generator upgrade._

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.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.

[Bug]: ConnectOptions with multiple values do not get filled

2 participants