diff --git a/src/google/adk/tools/openapi_tool/common/common.py b/src/google/adk/tools/openapi_tool/common/common.py index 7187b1bd..1df3125e 100644 --- a/src/google/adk/tools/openapi_tool/common/common.py +++ b/src/google/adk/tools/openapi_tool/common/common.py @@ -64,11 +64,9 @@ class ApiParameter(BaseModel): required: bool = False def model_post_init(self, _: Any): - self.py_name = ( - self.py_name - if self.py_name - else rename_python_keywords(_to_snake_case(self.original_name)) - ) + if not self.py_name: + inferred_name = rename_python_keywords(_to_snake_case(self.original_name)) + self.py_name = inferred_name or self._default_py_name() if isinstance(self.param_schema, str): self.param_schema = Schema.model_validate_json(self.param_schema) @@ -77,6 +75,16 @@ class ApiParameter(BaseModel): self.type_hint = TypeHintHelper.get_type_hint(self.param_schema) return self + def _default_py_name(self) -> str: + location_defaults = { + 'body': 'body', + 'query': 'query_param', + 'path': 'path_param', + 'header': 'header_param', + 'cookie': 'cookie_param', + } + return location_defaults.get(self.param_location or '', 'value') + @model_serializer def _serialize(self): return { diff --git a/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py b/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py index 06d692a2..326ff678 100644 --- a/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +++ b/src/google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py @@ -139,10 +139,19 @@ class OperationParser: ) ) else: + # Prefer explicit body name to avoid empty keys when schema lacks type + # information (e.g., oneOf/anyOf/allOf) while retaining legacy behavior + # for simple scalar types. + if schema and (schema.oneOf or schema.anyOf or schema.allOf): + param_name = 'body' + elif not schema or not schema.type: + param_name = 'body' + else: + param_name = '' + self._params.append( - # Empty name for unnamed body param ApiParameter( - original_name='', + original_name=param_name, param_location='body', param_schema=schema, description=description, diff --git a/tests/unittests/tools/openapi_tool/common/test_common.py b/tests/unittests/tools/openapi_tool/common/test_common.py index 47aeb79f..faece5be 100644 --- a/tests/unittests/tools/openapi_tool/common/test_common.py +++ b/tests/unittests/tools/openapi_tool/common/test_common.py @@ -74,6 +74,24 @@ class TestApiParameter: ) assert param.py_name == 'param_in' + def test_api_parameter_uses_location_default_when_name_missing(self): + schema = Schema(type='string') + param = ApiParameter( + original_name='', + param_location='body', + param_schema=schema, + ) + assert param.py_name == 'body' + + def test_api_parameter_uses_value_default_when_location_unknown(self): + schema = Schema(type='integer') + param = ApiParameter( + original_name='', + param_location='', + param_schema=schema, + ) + assert param.py_name == 'value' + def test_api_parameter_custom_py_name(self): schema = Schema(type='integer') param = ApiParameter( diff --git a/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_operation_parser.py b/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_operation_parser.py index 26cb944a..83741c97 100644 --- a/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_operation_parser.py +++ b/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_operation_parser.py @@ -164,6 +164,40 @@ def test_process_request_body_no_name(): assert parser._params[0].param_location == 'body' +def test_process_request_body_one_of_schema_assigns_name(): + """Ensures oneOf bodies result in a named parameter.""" + operation = Operation( + operationId='one_of_request', + requestBody=RequestBody( + content={ + 'application/json': MediaType( + schema=Schema( + oneOf=[ + Schema( + type='object', + properties={ + 'type': Schema(type='string'), + 'stage': Schema(type='string'), + }, + ) + ], + discriminator={'propertyName': 'type'}, + ) + ) + } + ), + responses={'200': Response(description='ok')}, + ) + parser = OperationParser(operation) + params = parser.get_parameters() + assert len(params) == 1 + assert params[0].original_name == 'body' + assert params[0].py_name == 'body' + schema = parser.get_json_schema() + assert 'body' in schema['properties'] + assert '' not in schema['properties'] + + def test_process_request_body_empty_object(): """Test _process_request_body with a schema that is of type object but with no properties.""" operation = Operation(