Fix query-only resource templates not matching URIs without query strings (#2323)

* Fix query-only resource templates not matching URIs without query strings

* apply the same fix to `has_resource`
This commit is contained in:
Josh Thomas 2025-11-01 10:27:37 -05:00 committed by GitHub
commit 08c49e62e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 109 additions and 3 deletions

View file

@ -236,7 +236,7 @@ class ResourceManager:
# Then check templates (local and mounted) only if not found in concrete resources
templates = await self.get_resource_templates()
for template_key in templates:
if match_uri_template(uri_str, template_key):
if match_uri_template(uri_str, template_key) is not None:
return True
return False
@ -262,7 +262,7 @@ class ResourceManager:
templates = await self.get_resource_templates()
for storage_key, template in templates.items():
# Try to match against the storage key (which might be a custom key)
if params := match_uri_template(uri_str, storage_key):
if (params := match_uri_template(uri_str, storage_key)) is not None:
try:
return await template.create_resource(
uri_str,
@ -318,7 +318,7 @@ class ResourceManager:
# 1b. Check local templates if not found in concrete resources
for key, template in self._templates.items():
if params := match_uri_template(uri_str, key):
if (params := match_uri_template(uri_str, key)) is not None:
try:
resource = await template.create_resource(uri_str, params=params)
return await resource.read()

View file

@ -567,6 +567,112 @@ class TestCustomResourceKeys:
await manager.get_resource("greet://world")
class TestQueryOnlyTemplates:
"""Test resource templates with only query parameters (no path params)."""
async def test_template_with_only_query_params_no_query_string(self):
"""Test that templates with only query params work without query string.
Regression test for bug where empty parameter dict {} was treated as falsy,
causing templates with only query parameters to fail when no query string
was provided in the URI.
"""
manager = ResourceManager()
def get_config(format: str = "json") -> str:
return f"Config in {format} format"
template = ResourceTemplate.from_function(
fn=get_config,
uri_template="data://config{?format}",
name="config",
)
manager.add_template(template)
# Should work without query param (uses default)
resource = await manager.get_resource("data://config")
content = await resource.read()
assert content == "Config in json format"
# Should also work via read_resource
content = await manager.read_resource("data://config")
assert content == "Config in json format"
async def test_template_with_only_query_params_with_query_string(self):
"""Test that templates with only query params work with query string."""
manager = ResourceManager()
def get_config(format: str = "json") -> str:
return f"Config in {format} format"
template = ResourceTemplate.from_function(
fn=get_config,
uri_template="data://config{?format}",
name="config",
)
manager.add_template(template)
# Should work with query param (overrides default)
resource = await manager.get_resource("data://config?format=xml")
content = await resource.read()
assert content == "Config in xml format"
# Should also work via read_resource
content = await manager.read_resource("data://config?format=xml")
assert content == "Config in xml format"
async def test_template_with_only_multiple_query_params(self):
"""Test template with only multiple query parameters."""
manager = ResourceManager()
def get_data(format: str = "json", limit: int = 10) -> str:
return f"Data in {format} (limit: {limit})"
template = ResourceTemplate.from_function(
fn=get_data,
uri_template="data://items{?format,limit}",
name="items",
)
manager.add_template(template)
# No query params - use all defaults
content = await manager.read_resource("data://items")
assert content == "Data in json (limit: 10)"
# Partial query params
content = await manager.read_resource("data://items?format=xml")
assert content == "Data in xml (limit: 10)"
# All query params
content = await manager.read_resource("data://items?format=xml&limit=20")
assert content == "Data in xml (limit: 20)"
async def test_has_resource_with_query_only_template(self):
"""Test that has_resource() works with query-only templates.
Regression test for bug where empty parameter dict {} was treated as falsy,
causing has_resource() to return False for query-only templates when no
query string was provided.
"""
manager = ResourceManager()
def get_config(format: str = "json") -> str:
return f"Config in {format} format"
template = ResourceTemplate.from_function(
fn=get_config,
uri_template="data://config{?format}",
name="config",
)
manager.add_template(template)
# Should find resource without query param (uses default)
assert await manager.has_resource("data://config")
# Should also find resource with query param
assert await manager.has_resource("data://config?format=xml")
class TestResourceErrorHandling:
"""Test error handling in the ResourceManager."""