Add collision detection for hyphen/underscore param name normalization

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
William Easton 2026-04-14 13:30:41 -05:00
commit 66a91a59e8
No known key found for this signature in database
2 changed files with 30 additions and 2 deletions

View file

@ -551,10 +551,25 @@ class FunctionResourceTemplate(ResourceTemplate):
)
# Extract path and query parameters from URI template.
# Allow hyphens in names (RFC 6570) and normalize to underscores
# so they match Python function parameter names.
# Allow hyphens in names and normalize to underscores so they
# match Python function parameter names.
raw_path_params = set(re.findall(r"{([\w-]+)(?:\*)?}", uri_template))
raw_query_params = extract_query_params(uri_template)
# Detect collisions: two raw param names that normalize to the
# same Python identifier (e.g. {user-id} and {user_id}).
all_raw = raw_path_params | raw_query_params
seen: dict[str, str] = {}
for raw_name in sorted(all_raw):
normalized = raw_name.replace("-", "_")
if normalized in seen:
raise ValueError(
f"URI template parameters '{seen[normalized]}' and "
f"'{raw_name}' both normalize to '{normalized}'. "
f"Use one or the other, not both."
)
seen[normalized] = raw_name
path_params = {p.replace("-", "_") for p in raw_path_params}
query_params = {p.replace("-", "_") for p in raw_query_params}
all_uri_params = path_params | query_params

View file

@ -850,6 +850,19 @@ class TestMalformedURITemplates:
assert result is not None
assert result["user_id"] == "alice"
def test_from_function_rejects_hyphen_underscore_collision(self):
"""Two raw param names that normalize to the same key are rejected."""
def handler(user_id: str) -> str:
return user_id
with pytest.raises(ValueError, match="both normalize to 'user_id'"):
ResourceTemplate.from_function(
fn=handler,
uri_template="test://{user-id}/{user_id}",
name="collision",
)
def test_build_regex_still_works_for_valid_templates(self):
regex = build_regex("test://{name}/{id}")
assert regex is not None