diff --git a/openapi_core/schema/operations/models.py b/openapi_core/schema/operations/models.py index f2acaa1..cb6c2fc 100644 --- a/openapi_core/schema/operations/models.py +++ b/openapi_core/schema/operations/models.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- """OpenAPI core operations models module""" -from openapi_core.schema.responses.exceptions import InvalidResponse class Operation(object): @@ -29,18 +28,3 @@ class Operation(object): def __getitem__(self, name): return self.parameters[name] - - def get_response(self, http_status='default'): - # @todo: move to Responses object - try: - return self.responses[http_status] - except KeyError: - # try range - http_status_range = '{0}XX'.format(http_status[0]) - if http_status_range in self.responses: - return self.responses[http_status_range] - - if 'default' not in self.responses: - raise InvalidResponse(http_status, self.responses) - - return self.responses['default'] diff --git a/openapi_core/schema/responses/exceptions.py b/openapi_core/schema/responses/exceptions.py index 577ad83..d7eb76b 100644 --- a/openapi_core/schema/responses/exceptions.py +++ b/openapi_core/schema/responses/exceptions.py @@ -7,16 +7,6 @@ class OpenAPIResponseError(OpenAPIMappingError): pass -@attr.s(hash=True) -class InvalidResponse(OpenAPIResponseError): - http_status = attr.ib() - responses = attr.ib() - - def __str__(self): - return "Unknown response http status: {0}".format( - str(self.http_status)) - - @attr.s(hash=True) class MissingResponseContent(OpenAPIResponseError): response = attr.ib() diff --git a/openapi_core/templating/responses/__init__.py b/openapi_core/templating/responses/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/openapi_core/templating/responses/exceptions.py b/openapi_core/templating/responses/exceptions.py new file mode 100644 index 0000000..3602ae6 --- /dev/null +++ b/openapi_core/templating/responses/exceptions.py @@ -0,0 +1,18 @@ +import attr + +from openapi_core.exceptions import OpenAPIError + + +class ResponseFinderError(OpenAPIError): + """Response finder error""" + + +@attr.s(hash=True) +class ResponseNotFound(ResponseFinderError): + """Find response error""" + http_status = attr.ib() + responses = attr.ib() + + def __str__(self): + return "Unknown response http status: {0}".format( + str(self.http_status)) diff --git a/openapi_core/templating/responses/finders.py b/openapi_core/templating/responses/finders.py new file mode 100644 index 0000000..8f6d3bd --- /dev/null +++ b/openapi_core/templating/responses/finders.py @@ -0,0 +1,23 @@ +from openapi_core.templating.responses.exceptions import ResponseNotFound + + +class ResponseFinder(object): + + def __init__(self, responses): + self.responses = responses + + def find(self, http_status='default'): + try: + return self.responses[http_status] + except KeyError: + pass + + # try range + http_status_range = '{0}XX'.format(http_status[0]) + if http_status_range in self.responses: + return self.responses[http_status_range] + + if 'default' not in self.responses: + raise ResponseNotFound(http_status, self.responses) + + return self.responses['default'] diff --git a/openapi_core/validation/response/validators.py b/openapi_core/validation/response/validators.py index a060c01..8daa865 100644 --- a/openapi_core/validation/response/validators.py +++ b/openapi_core/validation/response/validators.py @@ -1,11 +1,10 @@ """OpenAPI core validation response validators module""" from openapi_core.casting.schemas.exceptions import CastError from openapi_core.deserializing.exceptions import DeserializeError -from openapi_core.schema.responses.exceptions import ( - InvalidResponse, MissingResponseContent, -) +from openapi_core.schema.responses.exceptions import MissingResponseContent from openapi_core.templating.media_types.exceptions import MediaTypeFinderError from openapi_core.templating.paths.exceptions import PathError +from openapi_core.templating.responses.exceptions import ResponseFinderError from openapi_core.unmarshalling.schemas.enums import UnmarshalContext from openapi_core.unmarshalling.schemas.exceptions import ( UnmarshalError, ValidateError, @@ -27,7 +26,7 @@ class ResponseValidator(BaseValidator): operation_response = self._get_operation_response( operation, response) # don't process if operation errors - except InvalidResponse as exc: + except ResponseFinderError as exc: return ResponseValidationResult(errors=[exc, ]) data, data_errors = self._get_data(response, operation_response) @@ -43,7 +42,9 @@ class ResponseValidator(BaseValidator): ) def _get_operation_response(self, operation, response): - return operation.get_response(str(response.status_code)) + from openapi_core.templating.responses.finders import ResponseFinder + finder = ResponseFinder(operation.responses) + return finder.find(str(response.status_code)) def _validate_data(self, request, response): try: @@ -56,7 +57,7 @@ class ResponseValidator(BaseValidator): operation_response = self._get_operation_response( operation, response) # don't process if operation errors - except InvalidResponse as exc: + except ResponseFinderError as exc: return ResponseValidationResult(errors=[exc, ]) data, data_errors = self._get_data(response, operation_response) diff --git a/tests/integration/schema/test_link_spec.py b/tests/integration/schema/test_link_spec.py index b399b41..95a6226 100644 --- a/tests/integration/schema/test_link_spec.py +++ b/tests/integration/schema/test_link_spec.py @@ -6,7 +6,7 @@ class TestLinkSpec(object): def test_no_param(self, factory): spec_dict = factory.spec_from_file("data/v3.0/links.yaml") spec = create_spec(spec_dict) - resp = spec['/status']['get'].get_response() + resp = spec['/status']['get'].responses['default'] assert len(resp.links) == 1 @@ -20,7 +20,7 @@ class TestLinkSpec(object): def test_param(self, factory): spec_dict = factory.spec_from_file("data/v3.0/links.yaml") spec = create_spec(spec_dict) - resp = spec['/status/{resourceId}']['get'].get_response() + resp = spec['/status/{resourceId}']['get'].responses['default'] assert len(resp.links) == 1 diff --git a/tests/integration/validation/test_validators.py b/tests/integration/validation/test_validators.py index 0f3b504..3448588 100644 --- a/tests/integration/validation/test_validators.py +++ b/tests/integration/validation/test_validators.py @@ -8,14 +8,13 @@ from openapi_core.deserializing.exceptions import DeserializeError from openapi_core.extensions.models.models import BaseModel from openapi_core.schema.parameters.exceptions import MissingRequiredParameter from openapi_core.schema.request_bodies.exceptions import MissingRequestBody -from openapi_core.schema.responses.exceptions import ( - MissingResponseContent, InvalidResponse, -) +from openapi_core.schema.responses.exceptions import MissingResponseContent from openapi_core.shortcuts import create_spec from openapi_core.templating.media_types.exceptions import MediaTypeNotFound from openapi_core.templating.paths.exceptions import ( PathNotFound, OperationNotFound, ) +from openapi_core.templating.responses.exceptions import ResponseNotFound from openapi_core.testing import MockRequest, MockResponse from openapi_core.unmarshalling.schemas.exceptions import InvalidSchemaValue from openapi_core.validation.exceptions import InvalidSecurity @@ -450,7 +449,7 @@ class TestResponseValidator(object): result = validator.validate(request, response) assert len(result.errors) == 1 - assert type(result.errors[0]) == InvalidResponse + assert type(result.errors[0]) == ResponseNotFound assert result.data is None assert result.headers == {} diff --git a/tests/unit/schema/test_operations.py b/tests/unit/schema/test_operations.py index b84ce75..b1518ac 100644 --- a/tests/unit/schema/test_operations.py +++ b/tests/unit/schema/test_operations.py @@ -17,31 +17,3 @@ class TestSchemas(object): def test_iteritems(self, operation): for name in operation.parameters: assert operation[name] == operation.parameters[name] - - -class TestResponses(object): - - @pytest.fixture - def operation(self): - responses = { - '200': mock.sentinel.response_200, - '299': mock.sentinel.response_299, - '2XX': mock.sentinel.response_2XX, - 'default': mock.sentinel.response_default, - } - return Operation('get', '/path', responses, parameters={}) - - def test_default(self, operation): - response = operation.get_response() - - assert response == operation.responses['default'] - - def test_range(self, operation): - response = operation.get_response('201') - - assert response == operation.responses['2XX'] - - def test_exact(self, operation): - response = operation.get_response('200') - - assert response == operation.responses['200'] diff --git a/tests/unit/templating/test_responses_finders.py b/tests/unit/templating/test_responses_finders.py new file mode 100644 index 0000000..ccc5ea3 --- /dev/null +++ b/tests/unit/templating/test_responses_finders.py @@ -0,0 +1,35 @@ +import mock +import pytest + +from openapi_core.templating.responses.finders import ResponseFinder + + +class TestResponses(object): + + @pytest.fixture(scope='class') + def responses(self): + return { + '200': mock.sentinel.response_200, + '299': mock.sentinel.response_299, + '2XX': mock.sentinel.response_2XX, + 'default': mock.sentinel.response_default, + } + + @pytest.fixture(scope='class') + def finder(self, responses): + return ResponseFinder(responses) + + def test_default(self, finder, responses): + response = finder.find() + + assert response == responses['default'] + + def test_range(self, finder, responses): + response = finder.find('201') + + assert response == responses['2XX'] + + def test_exact(self, finder, responses): + response = finder.find('200') + + assert response == responses['200']