fix(client): url-encode query parameter names#565
Conversation
`parameters_to_url_query` quoted every value but pasted keys in raw, including the `multi` branch where the key was stringified without any encoding. A query name that contained `&`, `=`, `#`, a space, etc. would corrupt the query string (break at the `&`, fuse into the previous pair at `=`, silently truncate at `#`, etc.). Encode keys with the same `quote()` the values use. Mirrored into `openapi/templates/api_client.mustache`. Session: https://session-bc38cc85.poseidon.rapidata.internal/ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-Authored-By: lino <lino@rapidata.ai>
Code ReviewOverviewThis PR fixes a real correctness (and minor security) bug: What's Good
Suggestions / Issues1. encoded_key = quote(str(k)) # '/' is in the default safe set and won't be encodedFor query parameter names, a literal encoded_key = quote(str(k), safe='')If you want to keep the PR minimal (and value encoding already has this gap), it's reasonable to leave this as a follow-up. 2. return "&".join(["=".join(map(str, item)) for item in new_params])After the fix, both key and value in every tuple are already 3. No tests There are currently no test files in the repo, so this isn't a blocker, but this function is a good candidate for a unit test given the bug history: # e.g., key with '&' and '=' should be fully encoded
assert client.parameters_to_url_query({"a&b": "c=d"}, {}) == "a%26b=c%3Dd"
# multi branch
assert client.parameters_to_url_query({"k": ["v1", "v 2"]}, {"k": "multi"}) == "k=v1&k=v%202"SummaryThe fix is correct and the important part — encoding keys — is done right. The two items worth actioning before merge are the |
Summary
ApiClient.parameters_to_url_queryurl-encoded every value but not the key:```python
new_params.extend((k, str(value)) for value in v) # multi branch, key raw
new_params.append((k, delimiter.join(quote(str(value)) for value in v))) # key raw
new_params.append((k, quote(str(v)))) # key raw
```
A key containing
&,=,#, space, or UTF-8 bytes would break the query string — the&terminates the pair early,=merges into the value,#truncates at the fragment, etc.Fix
Compute
encoded_key = quote(str(k))once per iteration and use it at every append site.Test plan
uv run pyright src/rapidata/rapidata_client→ 0 errorsmultibranch now also encodes its per-item value (it wasn't before — only the key fix needs it, but the symmetry is cheap and intentional).🔗 Session: https://session-bc38cc85.poseidon.rapidata.internal/