fix(uri): ? operator now appends to existing query string (#25831)

## Summary

Fixes #19782.

The `?` operator in `std/uri` was silently overwriting any query string
already present in the URI. This PR makes it append instead — which
matches the docstring ("Concatenates the query parameters") and the
natural expectation when chaining operations.

**Before:**
```nim
let u = parseUri("https://example.com/foo?existing=1") ? {"bar": "qux"}
echo $u  # https://example.com/foo?bar=qux  (existing=1 lost)
```

**After:**
```nim
let u = parseUri("https://example.com/foo?existing=1") ? {"bar": "qux"}
echo $u  # https://example.com/foo?existing=1&bar=qux
```

## Changes

- `lib/pure/uri.nim`: fix `?` to append with `&` when a query string
already exists; add example to `runnableExamples`
- `tests/stdlib/turi.nim`: two new test cases (append to existing query,
empty params preserve existing)
- `changelog.md`: entry under Standard library changes

## Notes

I work with Claude as a co-processor. I'm 56, came to programming late,
and this is genuinely how I learn and contribute. I understand what I'm
submitting, but I didn't write it alone. If your project prefers
human-only contributions, just say so and I'll close without friction.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: n0madgang <14005836+n0madgang@users.noreply.github.com>
This commit is contained in:
Aleksei Rybnikov
2026-06-08 15:58:44 -05:00
committed by GitHub
parent b000d4a32a
commit b6842c144d
3 changed files with 19 additions and 1 deletions

View File

@@ -84,6 +84,8 @@ parameter and result types, not just their source-level shape. Use
- `std/pegs` now correctly lexes UTF-8 bytes inside bare identifier-style
terminals, so case-insensitive matching of non-ASCII terms (e.g. ``\i café``)
works without single-quoting.
- `std/uri`: The `?` operator now appends query parameters to an existing query
string instead of replacing it. Fixes [#19782](https://github.com/nim-lang/Nim/issues/19782).
## Language changes

View File

@@ -487,11 +487,18 @@ func `/`*(x: Uri, path: string): Uri =
func `?`*(u: Uri, query: openArray[(string, string)]): Uri =
## Concatenates the query parameters to the specified URI object.
## If the URI already has a query string, the new parameters are appended.
runnableExamples:
let foo = parseUri("https://example.com") / "foo" ? {"bar": "qux"}
assert $foo == "https://example.com/foo?bar=qux"
let bar = parseUri("https://example.com/foo?existing=1") ? {"bar": "qux"}
assert $bar == "https://example.com/foo?existing=1&bar=qux"
result = u
result.query = encodeQuery(query)
let newQuery = encodeQuery(query)
if newQuery.len > 0:
if result.query.len > 0:
result.query.add('&')
result.query.add(newQuery)
func `$`*(u: Uri): string =
## Returns the string representation of the specified URI object.

View File

@@ -289,6 +289,15 @@ template main() =
var foo = parseUri("http://example.com") / "foo" ? {"do": "do", "bar": ""}
var foo1 = parseUri("http://example.com/foo?do=do&bar")
doAssert foo == foo1
block: # issue #19782: appends to existing query string
var foo = parseUri("http://example.com/foo?existing=1") ? {"bar": "qux"}
doAssert $foo == "http://example.com/foo?existing=1&bar=qux"
block: # issue #19782: empty params list preserves existing query
var foo = parseUri("http://example.com/foo?existing=1") ? {:}
doAssert $foo == "http://example.com/foo?existing=1"
block: # issue #19782: empty params on uri without query is a no-op
var foo = parseUri("http://example.com/foo") ? {:}
doAssert $foo == "http://example.com/foo"
block: # getDataUri, dataUriBase64
doAssert getDataUri("", "text/plain") == "data:text/plain;charset=utf-8;base64,"