mirror of
https://github.com/correl/openapi-core.git
synced 2024-11-24 19:19:56 +00:00
Merge pull request #309 from p1c2u/feature/response-finder
Response finder
This commit is contained in:
commit
0865a4f54f
10 changed files with 88 additions and 66 deletions
|
@ -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']
|
||||
|
|
|
@ -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()
|
||||
|
|
0
openapi_core/templating/responses/__init__.py
Normal file
0
openapi_core/templating/responses/__init__.py
Normal file
18
openapi_core/templating/responses/exceptions.py
Normal file
18
openapi_core/templating/responses/exceptions.py
Normal file
|
@ -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))
|
23
openapi_core/templating/responses/finders.py
Normal file
23
openapi_core/templating/responses/finders.py
Normal file
|
@ -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']
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 == {}
|
||||
|
||||
|
|
|
@ -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']
|
||||
|
|
35
tests/unit/templating/test_responses_finders.py
Normal file
35
tests/unit/templating/test_responses_finders.py
Normal file
|
@ -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']
|
Loading…
Reference in a new issue