Key query expansion off the template, not the value type

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAKNJthovhc5VSi4MTiZUe
This commit is contained in:
Sai Mouli 2026-08-07 07:56:58 +05:30
commit d709bb1aba
2 changed files with 24 additions and 4 deletions

View file

@ -197,8 +197,12 @@ def expand_uri_template(uri_template: str, params: dict[str, Any]) -> str:
names = [n.strip() for n in match.group(1).split(",")]
parts = []
for name in names:
# `{?tags*}` is the explode form; the value is a list, emitted as a
# repeated key so it round-trips back through match_uri_template.
# The template decides the serialization, not the runtime value:
# `{?tags*}` emits a repeated key, `{?tags}` stays a single value.
# Keying off the value type instead would expand a list under a
# plain `{?tags}`, which match_uri_template then reads back as just
# its first element.
exploded = name.endswith("*")
name = name.removesuffix("*")
underscored = name.replace("-", "_")
if name in params:
@ -207,8 +211,10 @@ def expand_uri_template(uri_template: str, params: dict[str, Any]) -> str:
value = params[underscored]
else:
continue
values = value if isinstance(value, (list, tuple)) else [value]
parts.extend(f"{quote(name)}={quote(str(v))}" for v in values)
if exploded and isinstance(value, (list, tuple)):
parts.extend(f"{quote(name)}={quote(str(v))}" for v in value)
else:
parts.append(f"{quote(name)}={quote(str(value))}")
if parts:
return "?" + "&".join(parts)
return ""

View file

@ -1119,6 +1119,20 @@ class TestMatchExpandRoundTrip:
assert params is not None
assert expand_uri_template(template, params) == uri
def test_plain_query_param_does_not_repeat_a_sequence_value(self):
"""The template decides repeatability, not the runtime value's type.
A list handed to a plain `{?tags}` must stay one value: expanding it as
a repeated key produced a URI that matched back to only its first
element, contradicting the scalar contract `{?tags}` documents.
"""
uri = expand_uri_template("test://x{?tags}", {"tags": ["a", "b"]})
assert uri.count("tags=") == 1
params = match_uri_template(uri, "test://x{?tags}")
assert params is not None
assert expand_uri_template("test://x{?tags}", params) == uri
@pytest.mark.parametrize(
"template, params",
[