diff --git a/plugins/communication_protocols/http/pyproject.toml b/plugins/communication_protocols/http/pyproject.toml index 10bb447..ef2cdda 100644 --- a/plugins/communication_protocols/http/pyproject.toml +++ b/plugins/communication_protocols/http/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "utcp-http" -version = "1.1.8" +version = "1.1.9" authors = [ { name = "UTCP Contributors" }, ] 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 0054b29..dc930f0 100644 --- a/plugins/communication_protocols/http/src/utcp_http/openapi_converter.py +++ b/plugins/communication_protocols/http/src/utcp_http/openapi_converter.py @@ -29,9 +29,15 @@ from utcp_http.http_call_template import HttpCallTemplate from utcp_http._security import ensure_secure_url, is_loopback_url -# HTTP methods that HttpCallTemplate.http_method accepts. Kept as the single -# source of truth for both the operation loop filter and per-operation -# validation so the two can never drift apart. +# All HTTP methods that OpenAPI defines as operation fields on a Path Item +# Object. The conversion loop uses this to tell operations apart from the other +# path-item keys (parameters, summary, $ref, servers, ...), so that genuinely +# unsupported operations still reach _create_tool and get a warning rather than +# being silently dropped by the loop. +OPENAPI_OPERATION_METHODS: Tuple[str, ...] = ("get", "put", "post", "delete", "options", "head", "patch", "trace") + +# The subset of HTTP methods that HttpCallTemplate.http_method accepts. +# _create_tool validates against this and skips anything else with a warning. SUPPORTED_HTTP_METHODS: Tuple[str, ...] = ("GET", "POST", "PUT", "DELETE", "PATCH") class OpenApiConverter: @@ -190,7 +196,7 @@ def convert(self) -> UtcpManual: for path, path_item in self.spec.get("paths", {}).items(): for method, operation in path_item.items(): - if method.upper() in SUPPORTED_HTTP_METHODS: + if method.lower() in OPENAPI_OPERATION_METHODS: tool = self._create_tool(path, method, operation, base_url) if tool: tools.append(tool) diff --git a/plugins/communication_protocols/http/tests/test_openapi_converter.py b/plugins/communication_protocols/http/tests/test_openapi_converter.py index 1cb3ac6..9cf7709 100644 --- a/plugins/communication_protocols/http/tests/test_openapi_converter.py +++ b/plugins/communication_protocols/http/tests/test_openapi_converter.py @@ -220,7 +220,7 @@ def test_openapi_converter_parameter_examples(): assert example_value["email"] == "jane@example.com" -def test_openapi_converter_skips_unsupported_methods(): +def test_openapi_converter_skips_unsupported_methods(capsys): """Operations with HTTP methods HttpCallTemplate cannot represent are skipped, not crashed on.""" openapi_spec = { "openapi": "3.0.0", @@ -254,6 +254,13 @@ def test_openapi_converter_skips_unsupported_methods(): tool_names = {tool.name for tool in manual.tools} assert tool_names == {"listThings"} + # Unsupported operations must reach _create_tool and emit a skip warning, + # not be silently dropped by the loop filter. + stderr = capsys.readouterr().err + assert "optionsThings" in stderr + assert "headThings" in stderr + assert "traceThings" in stderr + def test_openapi_converter_schema_level_examples_normalized(): """Examples declared at the schema level (not the media type) are normalized into 'examples'."""