diff --git a/openapi_core/exceptions.py b/openapi_core/exceptions.py index 0d338844..cfcf39d4 100644 --- a/openapi_core/exceptions.py +++ b/openapi_core/exceptions.py @@ -36,8 +36,21 @@ class OpenAPIRequestBodyError(OpenAPIError): pass +class MissingRequestBodyError(OpenAPIRequestBodyError): + """Missing request body error""" + pass + + +@attr.s(hash=True) +class MissingRequestBody(MissingRequestBodyError): + request = attr.ib() + + def __str__(self): + return "Missing request body" + + @attr.s(hash=True) -class MissingRequestBody(OpenAPIRequestBodyError): +class MissingRequiredRequestBody(MissingRequestBodyError): request = attr.ib() def __str__(self): diff --git a/openapi_core/validation/request/validators.py b/openapi_core/validation/request/validators.py index fb01adf4..81d7e202 100644 --- a/openapi_core/validation/request/validators.py +++ b/openapi_core/validation/request/validators.py @@ -5,7 +5,8 @@ from openapi_core.casting.schemas.exceptions import CastError from openapi_core.deserializing.exceptions import DeserializeError from openapi_core.exceptions import ( - MissingRequiredParameter, MissingParameter, MissingRequestBody, + MissingRequiredParameter, MissingParameter, + MissingRequiredRequestBody, MissingRequestBody, ) from openapi_core.security.exceptions import SecurityError from openapi_core.schema.parameters import get_aslist, get_explode @@ -175,15 +176,18 @@ def _get_body(self, request, operation): return None, [] request_body = operation / 'requestBody' + try: - media_type, mimetype = self._get_media_type( - request_body / 'content', request) - except MediaTypeFinderError as exc: + raw_body = self._get_body_value(request_body, request) + except MissingRequiredRequestBody as exc: return None, [exc, ] + except MissingRequestBody: + return None, [] try: - raw_body = self._get_body_value(request_body, request) - except MissingRequestBody as exc: + media_type, mimetype = self._get_media_type( + request_body / 'content', request) + except MediaTypeFinderError as exc: return None, [exc, ] try: @@ -233,8 +237,9 @@ def _get_parameter_value(self, param, request): return location[param['name']] def _get_body_value(self, request_body, request): - required = request_body.getkey('required', False) - if not request.body and required: + if not request.body: + if request_body.getkey('required', False): + raise MissingRequiredRequestBody(request) raise MissingRequestBody(request) return request.body diff --git a/tests/integration/data/v3.0/petstore.yaml b/tests/integration/data/v3.0/petstore.yaml index 55813c4f..01b90f4a 100644 --- a/tests/integration/data/v3.0/petstore.yaml +++ b/tests/integration/data/v3.0/petstore.yaml @@ -202,6 +202,22 @@ paths: description: Null response default: $ref: "#/components/responses/ErrorResponse" + delete: + summary: Delete tags + operationId: deleteTag + tags: + - tags + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/TagDelete' + responses: + '200': + description: Null response + default: + $ref: "#/components/responses/ErrorResponse" components: schemas: Utctime: @@ -335,6 +351,18 @@ components: name: type: string additionalProperties: false + TagDelete: + type: object + x-model: TagDelete + required: + - ids + properties: + ids: + type: array + items: + type: integer + format: int64 + additionalProperties: false TagList: type: array items: diff --git a/tests/integration/validation/test_petstore.py b/tests/integration/validation/test_petstore.py index 56065395..7f2af239 100644 --- a/tests/integration/validation/test_petstore.py +++ b/tests/integration/validation/test_petstore.py @@ -1230,3 +1230,39 @@ def test_post_tags_created_invalid_type( assert response_result.data.correlationId == correlationId assert response_result.data.rootCause == rootCause assert response_result.data.additionalinfo == additionalinfo + + def test_delete_tags_with_requestbody( + self, spec, response_validator): + host_url = 'http://petstore.swagger.io/v1' + path_pattern = '/v1/tags' + ids = [1, 2, 3] + data_json = { + 'ids': ids, + } + data = json.dumps(data_json) + request = MockRequest( + host_url, 'DELETE', '/tags', + path_pattern=path_pattern, data=data, + ) + + parameters = validate_parameters(spec, request) + body = validate_body(spec, request) + + assert parameters == RequestParameters() + assert isinstance(body, BaseModel) + assert body.ids == ids + + def test_delete_tags_no_requestbody( + self, spec, response_validator): + host_url = 'http://petstore.swagger.io/v1' + path_pattern = '/v1/tags' + request = MockRequest( + host_url, 'DELETE', '/tags', + path_pattern=path_pattern, + ) + + parameters = validate_parameters(spec, request) + body = validate_body(spec, request) + + assert parameters == RequestParameters() + assert body is None diff --git a/tests/integration/validation/test_validators.py b/tests/integration/validation/test_validators.py index 0e4873fb..235341d6 100644 --- a/tests/integration/validation/test_validators.py +++ b/tests/integration/validation/test_validators.py @@ -7,7 +7,8 @@ from openapi_core.deserializing.exceptions import DeserializeError from openapi_core.extensions.models.models import BaseModel from openapi_core.exceptions import ( - MissingRequiredParameter, MissingRequestBody, MissingResponseContent, + MissingRequiredParameter, MissingRequiredRequestBody, + MissingResponseContent, ) from openapi_core.shortcuts import create_spec from openapi_core.templating.media_types.exceptions import MediaTypeNotFound @@ -154,7 +155,7 @@ def test_missing_body(self, validator): result = validator.validate(request) assert len(result.errors) == 1 - assert type(result.errors[0]) == MissingRequestBody + assert type(result.errors[0]) == MissingRequiredRequestBody assert result.body is None assert result.parameters == RequestParameters( header={ @@ -166,6 +167,7 @@ def test_missing_body(self, validator): ) def test_invalid_content_type(self, validator): + data = "csv,data" headers = { 'api_key': self.api_key_encoded, } @@ -174,7 +176,7 @@ def test_invalid_content_type(self, validator): } request = MockRequest( 'https://development.gigantic-server.com', 'post', '/v1/pets', - path_pattern='/v1/pets', mimetype='text/csv', + path_pattern='/v1/pets', mimetype='text/csv', data=data, headers=headers, cookies=cookies, )