diff --git a/connexion/decorators/parameter.py b/connexion/decorators/parameter.py index 8aab04f5c..3ae1e0fb0 100644 --- a/connexion/decorators/parameter.py +++ b/connexion/decorators/parameter.py @@ -211,18 +211,21 @@ def get_arguments( ) ) - if operation.method.upper() in ["PATCH", "POST", "PUT"]: - ret.update( - _get_body_argument( - body, - operation=operation, - arguments=arguments, - has_kwargs=has_kwargs, - sanitize=sanitize, - content_type=content_type, - ) + if operation.method.upper() == "TRACE": + # TRACE requests MUST NOT include a body (RFC7231 section 4.3.8) + return ret + + ret.update( + _get_body_argument( + body, + operation=operation, + arguments=arguments, + has_kwargs=has_kwargs, + sanitize=sanitize, + content_type=content_type, ) - ret.update(_get_file_arguments(files, arguments, has_kwargs)) + ) + ret.update(_get_file_arguments(files, arguments, has_kwargs)) return ret @@ -377,6 +380,9 @@ def _get_body_argument( if len(arguments) <= 0 and not has_kwargs: return {} + if not operation.is_request_body_defined: + return {} + body_name = sanitize(operation.body_name(content_type)) if content_type in FORM_CONTENT_TYPES: diff --git a/connexion/operations/abstract.py b/connexion/operations/abstract.py index 9087db069..9a79c293e 100644 --- a/connexion/operations/abstract.py +++ b/connexion/operations/abstract.py @@ -87,6 +87,11 @@ def method(self): def request_body(self): """The request body for this operation""" + @property + def is_request_body_defined(self) -> bool: + """Whether the request body is defined for this operation""" + return self.request_body != {} + @property def path(self): """ diff --git a/docs/v3.rst b/docs/v3.rst index 80ebd9c31..8c8b4859b 100644 --- a/docs/v3.rst +++ b/docs/v3.rst @@ -126,6 +126,7 @@ Smaller breaking changes * The ``MethodViewResolver`` has been renamed to ``MethodResolver``, and a new ``MethodViewResolver`` has been added to work with Flask's ``MethodView`` specifically. * Built-in support for uWSGI has been removed. You can re-add this functionality using a custom middleware. +* The request body is now passed through for ``GET``, ``HEAD``, ``DELETE``, ``CONNECT`` and ``OPTIONS`` methods as well. Non-breaking changes diff --git a/tests/api/test_parameters.py b/tests/api/test_parameters.py index ab58fd193..f7b84b45a 100644 --- a/tests/api/test_parameters.py +++ b/tests/api/test_parameters.py @@ -347,6 +347,18 @@ def test_body_not_allowed_additional_properties(simple_app): assert "Additional properties are not allowed" in response["detail"] +def test_body_in_get_request(simple_app): + app_client = simple_app.test_client() + body = {"body1": "bodyString"} + resp = app_client.request( + "GET", + "/v1.0/body-in-get-request", + json=body, + ) + assert resp.status_code == 200 + assert resp.json() == body + + def test_bool_as_default_param(simple_app): app_client = simple_app.test_client() resp = app_client.get("/v1.0/test-bool-param") diff --git a/tests/decorators/test_parameter.py b/tests/decorators/test_parameter.py index 8356339f3..dd404d0df 100644 --- a/tests/decorators/test_parameter.py +++ b/tests/decorators/test_parameter.py @@ -21,6 +21,7 @@ def test_sync_injection(): request = MagicMock(name="request") request.path_params = {"p1": "123"} + request.get_body.return_value = {} func = MagicMock() @@ -28,6 +29,7 @@ def handler(**kwargs): func(**kwargs) operation = MagicMock(name="operation") + operation.is_request_body_defined = False operation.body_name = lambda _: "body" with TestContext(operation=operation): @@ -43,6 +45,8 @@ def handler(**kwargs): async def test_async_injection(): request = AsyncMock(name="request") request.path_params = {"p1": "123"} + request.get_body.return_value = {} + request.files.return_value = {} func = MagicMock() @@ -50,6 +54,7 @@ async def handler(**kwargs): func(**kwargs) operation = MagicMock(name="operation") + operation.is_request_body_defined = False operation.body_name = lambda _: "body" with TestContext(operation=operation): @@ -62,6 +67,7 @@ async def handler(**kwargs): def test_sync_injection_with_context(): request = MagicMock(name="request") request.path_params = {"p1": "123"} + request.get_body.return_value = {} func = MagicMock() @@ -71,6 +77,7 @@ def handler(context_, **kwargs): context = {"test": "success"} operation = MagicMock(name="operation") + operation.is_request_body_defined = False operation.body_name = lambda _: "body" with TestContext(context=context, operation=operation): @@ -86,6 +93,8 @@ def handler(context_, **kwargs): async def test_async_injection_with_context(): request = AsyncMock(name="request") request.path_params = {"p1": "123"} + request.get_body.return_value = {} + request.files.return_value = {} func = MagicMock() @@ -95,6 +104,7 @@ async def handler(context_, **kwargs): context = {"test": "success"} operation = MagicMock(name="operation") + operation.is_request_body_defined = False operation.body_name = lambda _: "body" with TestContext(context=context, operation=operation): diff --git a/tests/fakeapi/hello/__init__.py b/tests/fakeapi/hello/__init__.py index b92347112..1921ac930 100644 --- a/tests/fakeapi/hello/__init__.py +++ b/tests/fakeapi/hello/__init__.py @@ -580,6 +580,10 @@ def test_body_not_allowed_additional_properties(body): return body +def test_body_in_get_request(body): + return body + + def post_wrong_content_type(): return "NOT OK" diff --git a/tests/fixtures/simple/openapi.yaml b/tests/fixtures/simple/openapi.yaml index 9f9e0da05..8d66a29a4 100644 --- a/tests/fixtures/simple/openapi.yaml +++ b/tests/fixtures/simple/openapi.yaml @@ -1079,6 +1079,20 @@ paths: body1: type: string additionalProperties: false + /body-in-get-request: + get: + operationId: fakeapi.hello.test_body_in_get_request + responses: + '200': + description: OK + requestBody: + content: + application/json: + schema: + type: object + properties: + body1: + type: string /get_non_conforming_response: get: operationId: fakeapi.hello.get_empty_dict diff --git a/tests/fixtures/simple/swagger.yaml b/tests/fixtures/simple/swagger.yaml index bb50c787f..e0b5f36ec 100644 --- a/tests/fixtures/simple/swagger.yaml +++ b/tests/fixtures/simple/swagger.yaml @@ -951,6 +951,27 @@ paths: 200: description: OK + /body-in-get-request: + get: + operationId: fakeapi.hello.test_body_in_get_request + consumes: + - application/json + produces: + - application/json + parameters: + - name: $body + description: A request body in a GET method. + in: body + required: true + schema: + type: object + properties: + body1: + type: string + responses: + 200: + description: OK + /get_non_conforming_response: get: operationId: fakeapi.hello.get_empty_dict