diff --git a/plugins/communication_protocols/http/src/utcp_http/openapi_converter.py b/plugins/communication_protocols/http/src/utcp_http/openapi_converter.py index c333a27..426a348 100644 --- a/plugins/communication_protocols/http/src/utcp_http/openapi_converter.py +++ b/plugins/communication_protocols/http/src/utcp_http/openapi_converter.py @@ -775,6 +775,11 @@ def _extract_outputs(self, operation: Dict[str, Any]) -> JsonSchema: break elif "schema" in success_response: # OpenAPI 2.0 json_schema = success_response.get("schema") + # Swagger response examples map MIME types to raw values, not + # OpenAPI 3 Example Objects. Normalize before merging with schema examples. + examples = success_response.get("examples") + if isinstance(examples, dict): + media_type_obj = {"examples": list(examples.values())} if not json_schema: return JsonSchema() diff --git a/plugins/communication_protocols/http/tests/test_openapi_response_examples.py b/plugins/communication_protocols/http/tests/test_openapi_response_examples.py new file mode 100644 index 0000000..211f010 --- /dev/null +++ b/plugins/communication_protocols/http/tests/test_openapi_response_examples.py @@ -0,0 +1,109 @@ +from copy import deepcopy + +import pytest + +from utcp_http.openapi_converter import OpenApiConverter + + +def swagger_spec(response, status="200"): + return { + "swagger": "2.0", + "info": {"title": "Example API", "version": "1.0"}, + "host": "api.example.com", + "schemes": ["https"], + "paths": { + "/items": { + "get": { + "operationId": "getItems", + "responses": {status: response}, + } + } + }, + } + + +@pytest.mark.parametrize( + "schema, example", + [ + ({"type": "object"}, {"value": "literal", "summary": "payload"}), + ({"type": "object"}, {"$ref": "#/definitions/Result"}), + ({"type": "object"}, {"externalValue": "https://example.com/sample"}), + ({"type": "array", "items": {"type": "integer"}}, [1, 2]), + ({"type": "array", "items": {"type": "integer"}}, []), + ({"type": "boolean"}, False), + ({"type": "integer"}, 0), + ({"type": "string"}, ""), + ({"type": "object", "x-nullable": True}, None), + ], +) +def test_swagger_response_examples_preserve_raw_values(schema, example): + spec = swagger_spec({ + "description": "OK", + "schema": schema, + "examples": {"application/json": example}, + }) + spec["definitions"] = {"Result": {"type": "string", "example": "resolved"}} + original = deepcopy(spec) + + tool = OpenApiConverter(spec).convert().tools[0] + + assert tool.outputs.examples == [example] + assert tool.outputs.model_dump(by_alias=True)["examples"] == [example] + assert spec == original + + +@pytest.mark.parametrize("status", ["200", "201", "default"]) +def test_swagger_response_examples_merge_media_types_and_schema(status): + spec = swagger_spec({ + "description": "OK", + "schema": {"$ref": "#/definitions/Result"}, + "examples": { + "application/json": {"id": 1, "name": "widget"}, + "application/vnd.example+json": {"name": "widget", "id": 1}, + "application/xml": "widget", + }, + }, status=status) + spec["definitions"] = { + "Result": { + "type": "object", + "properties": {"id": {"type": "integer"}, "name": {"type": "string"}}, + "example": {"id": 2, "name": "other"}, + } + } + + tool = OpenApiConverter(spec).convert().tools[0] + + assert tool.outputs.properties["id"].type == "integer" + assert tool.outputs.examples == [ + {"id": 1, "name": "widget"}, + "widget", + {"id": 2, "name": "other"}, + ] + + +def test_swagger_response_examples_deduplicate_without_collapsing_json_types(): + spec = swagger_spec({ + "description": "OK", + "schema": {"description": "A JSON value", "example": True}, + "examples": { + "application/json": True, + "application/vnd.example+json": 1, + "application/vnd.other+json": True, + }, + }) + + examples = OpenApiConverter(spec).convert().tools[0].outputs.examples + + assert examples == [True, 1] + assert [type(value) for value in examples] == [bool, int] + + +@pytest.mark.parametrize("response_examples", [{}, None, ["not a MIME map"]]) +def test_swagger_response_without_example_map_keeps_schema_example(response_examples): + spec = swagger_spec({ + "description": "OK", + "schema": {"type": "string", "example": "schema example"}, + "examples": response_examples, + }) + + assert OpenApiConverter(spec).convert().tools[0].outputs.examples == ["schema example"]