From feb87f77503e501f3097db6a94c50fef7fae3d03 Mon Sep 17 00:00:00 2001 From: ninglinLiu Date: Fri, 30 Oct 2026 06:09:27 +0800 Subject: [PATCH] fix(openapi): handle request body media types with no schema RestApiTool._prepare_request_params read media_type_object.schema_.type unconditionally. OpenAPI allows content entries with an empty MediaType (no schema), which made every call raise AttributeError. Treat a missing schema like a non-object and send the single body parameter as raw data. Fixes #7838 This PR was prepared with AI assistance and reviewed/tested by the author. --- .../openapi_spec_parser/rest_api_tool.py | 5 ++- .../openapi_spec_parser/test_rest_api_tool.py | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py b/src/google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py index 18dbb70b259..65926ccefb9 100644 --- a/src/google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +++ b/src/google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py @@ -573,7 +573,10 @@ def _prepare_request_params( schema = media_type_object.schema_ body_data = None - if schema.type == "object": + # OpenAPI allows a media type with no schema (e.g. + # application/octet-stream: {}). Treat a missing schema like a + # non-object so the single body parameter is sent as raw data. + if schema is not None and schema.type == "object": body_data = {} for param in parameters: if param.param_location == "body" and param.py_name in kwargs: diff --git a/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_rest_api_tool.py b/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_rest_api_tool.py index 74d6cefd4f4..fe40026f413 100644 --- a/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_rest_api_tool.py +++ b/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_rest_api_tool.py @@ -2253,6 +2253,41 @@ def test_prepare_request_params_octet_stream( request_params["headers"]["Content-Type"] == "application/octet-stream" ) + def test_prepare_request_params_octet_stream_without_schema( + self, sample_endpoint, sample_auth_scheme, sample_auth_credential + ): + """Media types with no schema still send the body (OpenAPI allows {}).""" + mock_operation = Operation( + operationId="upload_contract", + requestBody=RequestBody( + content={"application/octet-stream": MediaType()} + ), + ) + tool = RestApiTool( + name="upload_contract", + description="upload", + endpoint=sample_endpoint, + operation=mock_operation, + auth_credential=sample_auth_credential, + auth_scheme=sample_auth_scheme, + ) + params = [ + ApiParameter( + original_name="", + py_name="body", + param_location="body", + param_schema=OpenAPISchema(), + ) + ] + kwargs = {"body": b"%PDF-1.7"} + + request_params = tool._prepare_request_params(params, kwargs) + + assert request_params["data"] == b"%PDF-1.7" + assert ( + request_params["headers"]["Content-Type"] == "application/octet-stream" + ) + def test_prepare_request_params_path_param( self, sample_endpoint, sample_auth_credential, sample_auth_scheme ):