mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-23 22:14:18 +02:00
Unify openapi 3.0 and 3.1 parsing
This commit is contained in:
parent
a4f6ecd0be
commit
2cce73a133
3 changed files with 346 additions and 606 deletions
|
|
@ -173,7 +173,6 @@ class OpenAPITool(Tool):
|
|||
for param_name, param_value in path_params.items():
|
||||
# Handle array path parameters with style 'simple' (comma-separated)
|
||||
# In OpenAPI, 'simple' is the default style for path parameters
|
||||
# and explode=False behavior for arrays
|
||||
param_info = next(
|
||||
(p for p in self._route.parameters if p.name == param_name), None
|
||||
)
|
||||
|
|
@ -186,9 +185,49 @@ class OpenAPITool(Tool):
|
|||
if is_array:
|
||||
# Format array values as comma-separated string
|
||||
# This follows the OpenAPI 'simple' style (default for path)
|
||||
# and explode=False behavior for arrays
|
||||
param_value = ",".join(str(item) for item in param_value)
|
||||
if all(
|
||||
isinstance(item, str | int | float | bool)
|
||||
for item in param_value
|
||||
):
|
||||
# Handle simple array types
|
||||
path = path.replace(
|
||||
f"{{{param_name}}}", ",".join(str(v) for v in param_value)
|
||||
)
|
||||
else:
|
||||
# Handle complex array types (containing objects/dicts)
|
||||
try:
|
||||
# Try to create a simple representation without Python syntax artifacts
|
||||
formatted_parts = []
|
||||
for item in param_value:
|
||||
if isinstance(item, dict):
|
||||
# For objects, serialize key-value pairs
|
||||
item_parts = []
|
||||
for k, v in item.items():
|
||||
item_parts.append(f"{k}:{v}")
|
||||
formatted_parts.append(".".join(item_parts))
|
||||
else:
|
||||
# Fallback for other complex types
|
||||
formatted_parts.append(str(item))
|
||||
|
||||
# Join parts with commas
|
||||
formatted_value = ",".join(formatted_parts)
|
||||
path = path.replace(f"{{{param_name}}}", formatted_value)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Failed to format complex array path parameter '{param_name}': {e}"
|
||||
)
|
||||
# Fallback to string representation, but remove Python syntax artifacts
|
||||
str_value = (
|
||||
str(param_value)
|
||||
.replace("[", "")
|
||||
.replace("]", "")
|
||||
.replace("'", "")
|
||||
.replace('"', "")
|
||||
)
|
||||
path = path.replace(f"{{{param_name}}}", str_value)
|
||||
continue
|
||||
|
||||
# Default handling for non-array parameters or non-array schemas
|
||||
path = path.replace(f"{{{param_name}}}", str(param_value))
|
||||
|
||||
# Prepare query parameters - filter out None and empty strings
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -188,3 +188,73 @@ async def test_integration_array_path_parameter(array_path_spec, mock_client):
|
|||
json=None,
|
||||
timeout=None,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_complex_nested_array_path_parameter(mock_client):
|
||||
"""Test handling of complex nested array path parameters."""
|
||||
# Create a route with a path parameter that contains nested objects in an array
|
||||
route = HTTPRoute(
|
||||
path="/report/{filters}",
|
||||
method="GET",
|
||||
operation_id="test-complex-filters",
|
||||
parameters=[
|
||||
ParameterInfo(
|
||||
name="filters",
|
||||
location="path",
|
||||
required=True,
|
||||
schema={
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": {"type": "string"},
|
||||
"value": {"type": "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
# Create the tool
|
||||
tool = OpenAPITool(
|
||||
client=mock_client,
|
||||
route=route,
|
||||
name="test-complex-filters",
|
||||
description="Test operation with complex filters",
|
||||
parameters={},
|
||||
)
|
||||
|
||||
# Test with a more complex path parameter
|
||||
# This would typically be serialized as JSON or a more complex format
|
||||
# But for path parameters with style=simple, it should be comma-separated
|
||||
complex_filters = [
|
||||
{"field": "status", "value": "active"},
|
||||
{"field": "type", "value": "user"},
|
||||
]
|
||||
|
||||
# Execute the request with complex filters
|
||||
await tool._execute_request(filters=complex_filters)
|
||||
|
||||
# The complex object should be properly serialized in the URL
|
||||
# For path parameters, this would typically need a custom serialization strategy
|
||||
# but our implementation should handle it safely
|
||||
call_args = mock_client.request.call_args
|
||||
|
||||
# Verify the request was made
|
||||
assert call_args is not None, "The request was not made"
|
||||
|
||||
# Get the called URL and verify it contains the serialized path parameter
|
||||
called_url = call_args[1].get("url")
|
||||
|
||||
# Check that the path parameter is handled (we don't expect perfect serialization,
|
||||
# but it should not cause errors and should maintain the array structure)
|
||||
assert "/report/" in called_url, "The URL should contain the path prefix"
|
||||
|
||||
# Check that it didn't just convert the objects to string representations
|
||||
# that include the Python object syntax
|
||||
assert "status" in called_url, "The URL should contain filter field names"
|
||||
assert "active" in called_url, "The URL should contain filter values"
|
||||
assert "}" not in called_url, "The URL should not contain Python object syntax"
|
||||
assert "{" not in called_url, "The URL should not contain Python object syntax"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue