From a143471988f5f29e7f0c814917f0235e17665404 Mon Sep 17 00:00:00 2001 From: sshandilya Date: Fri, 20 Jun 2025 10:16:01 +0000 Subject: [PATCH 1/2] added operationid validation in openapi spec --- .../connectors/openapi_plugin/openapi_parser.py | 5 ++++- .../openapi_plugin/no-operationid-openapi.yaml | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 python/tests/unit/connectors/openapi_plugin/no-operationid-openapi.yaml diff --git a/python/semantic_kernel/connectors/openapi_plugin/openapi_parser.py b/python/semantic_kernel/connectors/openapi_plugin/openapi_parser.py index 848bba8386eb..266b88724e3c 100644 --- a/python/semantic_kernel/connectors/openapi_plugin/openapi_parser.py +++ b/python/semantic_kernel/connectors/openapi_plugin/openapi_parser.py @@ -229,8 +229,11 @@ def create_rest_api_operations( for path, methods in paths.items(): for method, details in methods.items(): + # Validate that operationId exists + if "operationId" not in details: + raise PluginInitializationError(f"operationId missing, path: '{path}', method: '{method}'") request_method = method.lower() - operationId = details.get("operationId", path + "_" + request_method) + operationId = details["operationId"] summary = details.get("summary", None) description = details.get("description", None) diff --git a/python/tests/unit/connectors/openapi_plugin/no-operationid-openapi.yaml b/python/tests/unit/connectors/openapi_plugin/no-operationid-openapi.yaml new file mode 100644 index 000000000000..f621ca7d461b --- /dev/null +++ b/python/tests/unit/connectors/openapi_plugin/no-operationid-openapi.yaml @@ -0,0 +1,17 @@ +openapi: 3.0.3 +info: + title: Simple HTTPBin API + version: 1.0.0 +servers: + - url: https://httpbin.org +paths: + /get: + get: + summary: Simple GET request to httpbin.org + responses: + '200': + description: Successful response from httpbin + content: + application/json: + schema: + type: object From 1ef4fe68820a1e855776633b37dd46c372147ad0 Mon Sep 17 00:00:00 2001 From: sshandilya Date: Fri, 20 Jun 2025 10:43:49 +0000 Subject: [PATCH 2/2] adjust for ruff --- .../openapi_plugin/openapi_parser.py | 8 ++++- .../duplicate-operationid-openapi.yaml | 32 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 python/tests/unit/connectors/openapi_plugin/duplicate-operationid-openapi.yaml diff --git a/python/semantic_kernel/connectors/openapi_plugin/openapi_parser.py b/python/semantic_kernel/connectors/openapi_plugin/openapi_parser.py index 266b88724e3c..0ed36f209d00 100644 --- a/python/semantic_kernel/connectors/openapi_plugin/openapi_parser.py +++ b/python/semantic_kernel/connectors/openapi_plugin/openapi_parser.py @@ -206,6 +206,7 @@ def create_rest_api_operations( paths = parsed_document.get("paths", {}) request_objects = {} + unique_operation_ids_registered: set[str] = set() servers = parsed_document.get("servers", []) @@ -229,11 +230,16 @@ def create_rest_api_operations( for path, methods in paths.items(): for method, details in methods.items(): + request_method = method.lower() # Validate that operationId exists if "operationId" not in details: raise PluginInitializationError(f"operationId missing, path: '{path}', method: '{method}'") - request_method = method.lower() operationId = details["operationId"] + if operationId in unique_operation_ids_registered: + raise PluginInitializationError( + f"Duplicate operationId: '{operationId}', path: '{path}', method: '{method}'" + ) + unique_operation_ids_registered.add(operationId) summary = details.get("summary", None) description = details.get("description", None) diff --git a/python/tests/unit/connectors/openapi_plugin/duplicate-operationid-openapi.yaml b/python/tests/unit/connectors/openapi_plugin/duplicate-operationid-openapi.yaml new file mode 100644 index 000000000000..62162de97859 --- /dev/null +++ b/python/tests/unit/connectors/openapi_plugin/duplicate-operationid-openapi.yaml @@ -0,0 +1,32 @@ +openapi: 3.0.3 +info: + title: Simple HTTPBin API + version: 1.0.0 + +servers: + - url: https://httpbin.org + +paths: + /get: + get: + operationId: duplicateId + summary: Simple GET request to httpbin.org + responses: + '200': + description: Successful response from httpbin + content: + application/json: + schema: + type: object + + /ip: + get: + operationId: duplicateId + summary: Get client IP address from httpbin + responses: + '200': + description: Successful response with IP + content: + application/json: + schema: + type: object