Unify openapi 3.0 and 3.1 parsing

This commit is contained in:
Jeremiah Lowin 2025-05-20 12:12:47 -04:00
commit 2cce73a133
3 changed files with 346 additions and 606 deletions

View file

@ -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

View file

@ -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"