Skip to content

Commit 7111a91

Browse files
committed
Validators restructure
1 parent 734a467 commit 7111a91

File tree

14 files changed

+236
-215
lines changed

14 files changed

+236
-215
lines changed

openapi_core/shortcuts.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
from openapi_core.exceptions import OpenAPIParameterError, OpenAPIBodyError
77
from openapi_core.schema.specs.factories import SpecFactory
8-
from openapi_core.validators import RequestValidator, ResponseValidator
8+
from openapi_core.validation.request.validators import RequestValidator
9+
from openapi_core.validation.response.validators import ResponseValidator
910

1011

1112
def create_spec(spec_dict, spec_url=''):

openapi_core/validation/__init__.py

Whitespace-only changes.

openapi_core/validation/models.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"""OpenAPI core validation models module"""
2+
3+
4+
class BaseValidationResult(object):
5+
6+
def __init__(self, errors):
7+
self.errors = errors
8+
9+
def raise_for_errors(self):
10+
for error in self.errors:
11+
raise error

openapi_core/validation/request/__init__.py

Whitespace-only changes.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""OpenAPI core validation request models module"""
2+
from openapi_core.exceptions import OpenAPIMappingError
3+
4+
from openapi_core.validation.models import BaseValidationResult
5+
6+
7+
class RequestParameters(dict):
8+
9+
valid_locations = ['path', 'query', 'headers', 'cookies']
10+
11+
def __getitem__(self, location):
12+
self.validate_location(location)
13+
14+
return self.setdefault(location, {})
15+
16+
def __setitem__(self, location, value):
17+
raise NotImplementedError
18+
19+
@classmethod
20+
def validate_location(cls, location):
21+
if location not in cls.valid_locations:
22+
raise OpenAPIMappingError(
23+
"Unknown parameter location: {0}".format(str(location)))
24+
25+
26+
class RequestValidationResult(BaseValidationResult):
27+
28+
def __init__(self, errors, body=None, parameters=None):
29+
super(RequestValidationResult, self).__init__(errors)
30+
self.body = body
31+
self.parameters = parameters or RequestParameters()
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""OpenAPI core validation request validators module"""
2+
from six import iteritems
3+
4+
from openapi_core.exceptions import (
5+
OpenAPIMappingError, MissingParameter, MissingBody,
6+
)
7+
from openapi_core.validation.request.models import (
8+
RequestParameters, RequestValidationResult,
9+
)
10+
from openapi_core.validation.util import get_operation_pattern
11+
12+
13+
class RequestValidator(object):
14+
15+
def __init__(self, spec):
16+
self.spec = spec
17+
18+
def validate(self, request):
19+
errors = []
20+
body = None
21+
parameters = RequestParameters()
22+
23+
try:
24+
server = self.spec.get_server(request.full_url_pattern)
25+
# don't process if server errors
26+
except OpenAPIMappingError as exc:
27+
errors.append(exc)
28+
return RequestValidationResult(errors, body, parameters)
29+
30+
operation_pattern = get_operation_pattern(
31+
server.default_url, request.full_url_pattern
32+
)
33+
34+
try:
35+
operation = self.spec.get_operation(
36+
operation_pattern, request.method)
37+
# don't process if operation errors
38+
except OpenAPIMappingError as exc:
39+
errors.append(exc)
40+
return RequestValidationResult(errors, body, parameters)
41+
42+
for param_name, param in iteritems(operation.parameters):
43+
try:
44+
raw_value = self._get_raw_value(request, param)
45+
except MissingParameter as exc:
46+
if param.required:
47+
errors.append(exc)
48+
49+
if not param.schema or param.schema.default is None:
50+
continue
51+
raw_value = param.schema.default
52+
53+
try:
54+
value = param.unmarshal(raw_value)
55+
except OpenAPIMappingError as exc:
56+
errors.append(exc)
57+
else:
58+
parameters[param.location.value][param_name] = value
59+
60+
if operation.request_body is not None:
61+
try:
62+
media_type = operation.request_body[request.mimetype]
63+
except OpenAPIMappingError as exc:
64+
errors.append(exc)
65+
else:
66+
try:
67+
raw_body = self._get_raw_body(request)
68+
except MissingBody as exc:
69+
if operation.request_body.required:
70+
errors.append(exc)
71+
else:
72+
try:
73+
body = media_type.unmarshal(raw_body)
74+
except OpenAPIMappingError as exc:
75+
errors.append(exc)
76+
77+
return RequestValidationResult(errors, body, parameters)
78+
79+
def _get_raw_value(self, request, param):
80+
location = request.parameters[param.location.value]
81+
82+
try:
83+
raw = location[param.name]
84+
except KeyError:
85+
raise MissingParameter(
86+
"Missing required `{0}` parameter".format(param.name))
87+
88+
if param.aslist and param.explode:
89+
return location.getlist(param.name)
90+
91+
return raw
92+
93+
def _get_raw_body(self, request):
94+
if not request.body:
95+
raise MissingBody("Missing required request body")
96+
97+
return request.body

openapi_core/validation/response/__init__.py

Whitespace-only changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""OpenAPI core validation response models module"""
2+
from openapi_core.validation.models import BaseValidationResult
3+
4+
5+
class ResponseValidationResult(BaseValidationResult):
6+
7+
def __init__(self, errors, data=None, headers=None):
8+
super(ResponseValidationResult, self).__init__(errors)
9+
self.data = data
10+
self.headers = headers
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""OpenAPI core validation response validators module"""
2+
from openapi_core.exceptions import (
3+
OpenAPIMappingError, MissingBody, InvalidResponse,
4+
)
5+
from openapi_core.validation.response.models import ResponseValidationResult
6+
from openapi_core.validation.util import get_operation_pattern
7+
8+
9+
class ResponseValidator(object):
10+
11+
def __init__(self, spec):
12+
self.spec = spec
13+
14+
def validate(self, request, response):
15+
errors = []
16+
data = None
17+
headers = {}
18+
19+
try:
20+
server = self.spec.get_server(request.full_url_pattern)
21+
# don't process if server errors
22+
except OpenAPIMappingError as exc:
23+
errors.append(exc)
24+
return ResponseValidationResult(errors, data, headers)
25+
26+
operation_pattern = get_operation_pattern(
27+
server.default_url, request.full_url_pattern
28+
)
29+
30+
try:
31+
operation = self.spec.get_operation(
32+
operation_pattern, request.method)
33+
# don't process if operation errors
34+
except OpenAPIMappingError as exc:
35+
errors.append(exc)
36+
return ResponseValidationResult(errors, data, headers)
37+
38+
try:
39+
operation_response = operation.get_response(
40+
str(response.status_code))
41+
# don't process if invalid response status code
42+
except InvalidResponse as exc:
43+
errors.append(exc)
44+
return ResponseValidationResult(errors, data, headers)
45+
46+
if operation_response.content:
47+
try:
48+
media_type = operation_response[response.mimetype]
49+
except OpenAPIMappingError as exc:
50+
errors.append(exc)
51+
else:
52+
try:
53+
raw_data = self._get_raw_data(response)
54+
except MissingBody as exc:
55+
errors.append(exc)
56+
else:
57+
try:
58+
data = media_type.unmarshal(raw_data)
59+
except OpenAPIMappingError as exc:
60+
errors.append(exc)
61+
62+
return ResponseValidationResult(errors, data, headers)
63+
64+
def _get_raw_data(self, response):
65+
if not response.data:
66+
raise MissingBody("Missing required response data")
67+
68+
return response.data

openapi_core/validation/util.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""OpenAPI core validation util module"""
2+
from yarl import URL
3+
4+
5+
def get_operation_pattern(server_url, request_url_pattern):
6+
"""Return an updated request URL pattern with the server URL removed."""
7+
if server_url[-1] == "/":
8+
# operations have to start with a slash, so do not remove it
9+
server_url = server_url[:-1]
10+
if URL(server_url).is_absolute():
11+
return request_url_pattern.replace(server_url, "", 1)
12+
return URL(request_url_pattern).path_qs.replace(server_url, "", 1)

0 commit comments

Comments
 (0)