Skip to content

Commit 7249858

Browse files
authored
Merge pull request #9 from p1c2u/feature/request-validator
Request validator
2 parents a61a249 + d4306d2 commit 7249858

File tree

17 files changed

+603
-183
lines changed

17 files changed

+603
-183
lines changed

README.rst

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,31 @@ Now you can use it to validate and unmarshal requests
5151

5252
.. code-block:: python
5353
54-
from openapi_core import request_parameters_factory, request_body_factory
54+
from openapi_core.validators import RequestValidator
5555
56-
parameters = request_parameters_factory.create(request, spec)
57-
body = request_body_factory.create(request, spec)
56+
validator = RequestValidator(spec)
57+
result = validator.validate(request)
58+
59+
# raise errors if request invalid
60+
result.validate()
61+
62+
# get parameters
63+
path_params = result.parameters['path']
64+
query_params = result.parameters['query']
65+
66+
# get body
67+
body = result.body
68+
69+
Request object should implement BaseOpenAPIRequest interface. You can use FlaskOpenAPIRequest a Flask/Werkzeug request wrapper implementation:
70+
71+
.. code-block:: python
72+
73+
from openapi_core.validators import RequestValidator
74+
from openapi_core.wrappers import FlaskOpenAPIRequest
75+
76+
openapi_request = FlaskOpenAPIRequest(flask_request)
77+
validator = RequestValidator(spec)
78+
result = validator.validate(openapi_request)
5879
5980
Related projects
6081
================

openapi_core/__init__.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
"""OpenAPI core module"""
2-
from openapi_core.shortcuts import create_spec
3-
from openapi_core.wrappers import RequestParametersFactory, RequestBodyFactory
2+
from openapi_core.shortcuts import (
3+
create_spec, validate_parameters, validate_body,
4+
)
45

56
__author__ = 'Artur Maciąg'
67
__email__ = 'maciag.artur@gmail.com'
78
__version__ = '0.2.2'
89
__url__ = 'https://github.com/p1c2u/openapi-core'
910
__license__ = 'BSD 3-Clause License'
1011

11-
__all__ = ['create_spec', 'request_parameters_factory', 'request_body_factory']
12-
13-
request_parameters_factory = RequestParametersFactory()
14-
request_body_factory = RequestBodyFactory()
12+
__all__ = ['create_spec', 'validate_parameters', 'validate_body']

openapi_core/exceptions.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,65 @@ class OpenAPIMappingError(OpenAPIError):
99
pass
1010

1111

12-
class MissingParameterError(OpenAPIMappingError):
12+
class OpenAPIServerError(OpenAPIMappingError):
1313
pass
1414

1515

16-
class MissingPropertyError(OpenAPIMappingError):
16+
class OpenAPIOperationError(OpenAPIMappingError):
1717
pass
1818

1919

20-
class InvalidContentTypeError(OpenAPIMappingError):
20+
class InvalidValueType(OpenAPIMappingError):
2121
pass
2222

2323

24-
class InvalidOperationError(OpenAPIMappingError):
24+
class OpenAPIParameterError(OpenAPIMappingError):
2525
pass
2626

2727

28-
class InvalidServerError(OpenAPIMappingError):
28+
class OpenAPIBodyError(OpenAPIMappingError):
2929
pass
3030

3131

32-
class InvalidValueType(OpenAPIMappingError):
32+
class InvalidServer(OpenAPIServerError):
3333
pass
3434

3535

36-
class InvalidValue(OpenAPIMappingError):
36+
class InvalidOperation(OpenAPIOperationError):
3737
pass
3838

3939

40-
class EmptyValue(OpenAPIMappingError):
40+
class EmptyValue(OpenAPIParameterError):
4141
pass
4242

4343

44-
class UndefinedSchemaProperty(OpenAPIMappingError):
44+
class MissingParameter(OpenAPIParameterError):
45+
pass
46+
47+
48+
class InvalidParameterValue(OpenAPIParameterError):
49+
pass
50+
51+
52+
class MissingBody(OpenAPIBodyError):
53+
pass
54+
55+
56+
class InvalidMediaTypeValue(OpenAPIBodyError):
57+
pass
58+
59+
60+
class UndefinedSchemaProperty(OpenAPIBodyError):
61+
pass
62+
63+
64+
class MissingProperty(OpenAPIBodyError):
65+
pass
66+
67+
68+
class InvalidContentType(OpenAPIBodyError):
69+
pass
70+
71+
72+
class InvalidValue(OpenAPIMappingError):
4573
pass

openapi_core/media_types.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""OpenAPI core mediaTypes module"""
22
from six import iteritems
33

4+
from openapi_core.exceptions import InvalidValueType, InvalidMediaTypeValue
5+
46

57
class MediaType(object):
68
"""Represents an OpenAPI MediaType."""
@@ -13,7 +15,10 @@ def unmarshal(self, value):
1315
if not self.schema:
1416
return value
1517

16-
return self.schema.unmarshal(value)
18+
try:
19+
return self.schema.unmarshal(value)
20+
except InvalidValueType as exc:
21+
raise InvalidMediaTypeValue(str(exc))
1722

1823

1924
class MediaTypeGenerator(object):

openapi_core/parameters.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
import logging
33
import warnings
44

5-
from openapi_core.exceptions import EmptyValue
5+
from openapi_core.exceptions import (
6+
EmptyValue, InvalidValueType, InvalidParameterValue,
7+
)
68

79
log = logging.getLogger(__name__)
810

@@ -35,12 +37,15 @@ def unmarshal(self, value):
3537
if (self.location == "query" and value == "" and
3638
not self.allow_empty_value):
3739
raise EmptyValue(
38-
"Value of {0} parameter cannot be empty.".format(self.name))
40+
"Value of {0} parameter cannot be empty".format(self.name))
3941

4042
if not self.schema:
4143
return value
4244

43-
return self.schema.unmarshal(value)
45+
try:
46+
return self.schema.unmarshal(value)
47+
except InvalidValueType as exc:
48+
raise InvalidParameterValue(str(exc))
4449

4550

4651
class ParametersGenerator(object):

openapi_core/request_bodies.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""OpenAPI core requestBodies module"""
22
from functools import lru_cache
33

4+
from openapi_core.exceptions import InvalidContentType
45
from openapi_core.media_types import MediaTypeGenerator
56

67

@@ -12,7 +13,11 @@ def __init__(self, content, required=False):
1213
self.required = required
1314

1415
def __getitem__(self, mimetype):
15-
return self.content[mimetype]
16+
try:
17+
return self.content[mimetype]
18+
except KeyError:
19+
raise InvalidContentType(
20+
"Invalid mime type `{0}`".format(mimetype))
1621

1722

1823
class RequestBodyFactory(object):

openapi_core/schemas.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
from six import iteritems
1111

1212
from openapi_core.exceptions import (
13-
InvalidValueType, UndefinedSchemaProperty, MissingPropertyError,
14-
InvalidValue,
13+
InvalidValueType, UndefinedSchemaProperty, MissingProperty, InvalidValue,
1514
)
1615
from openapi_core.models import ModelFactory
1716

@@ -59,7 +58,8 @@ def cast(self, value):
5958
if value is None:
6059
if not self.nullable:
6160
raise InvalidValueType(
62-
"Failed to cast value of %s to %s", value, self.type,
61+
"Failed to cast value of {0} to {1}".format(
62+
value, self.type)
6363
)
6464
return self.default
6565

@@ -73,7 +73,7 @@ def cast(self, value):
7373
return cast_callable(value)
7474
except ValueError:
7575
raise InvalidValueType(
76-
"Failed to cast value of %s to %s", value, self.type,
76+
"Failed to cast value of {0} to {1}".format(value, self.type)
7777
)
7878

7979
def unmarshal(self, value):
@@ -88,7 +88,8 @@ def unmarshal(self, value):
8888

8989
if self.enum and casted not in self.enum:
9090
raise InvalidValue(
91-
"Value of %s not in enum choices: %s", value, str(self.enum),
91+
"Value of {0} not in enum choices: {1}".format(
92+
value, self.enum)
9293
)
9394

9495
return casted
@@ -115,7 +116,7 @@ def _unmarshal_object(self, value):
115116
prop_value = value[prop_name]
116117
except KeyError:
117118
if prop_name in self.required:
118-
raise MissingPropertyError(
119+
raise MissingProperty(
119120
"Missing schema property {0}".format(prop_name))
120121
if not prop.nullable and not prop.default:
121122
continue

openapi_core/shortcuts.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
from openapi_spec_validator.validators import Dereferencer
44
from openapi_spec_validator import default_handlers
55

6+
from openapi_core.exceptions import OpenAPIParameterError, OpenAPIBodyError
67
from openapi_core.specs import SpecFactory
8+
from openapi_core.validators import RequestValidator
79

810

911
def create_spec(spec_dict, spec_url=''):
@@ -12,3 +14,25 @@ def create_spec(spec_dict, spec_url=''):
1214
dereferencer = Dereferencer(spec_resolver)
1315
spec_factory = SpecFactory(dereferencer)
1416
return spec_factory.create(spec_dict, spec_url=spec_url)
17+
18+
19+
def validate_parameters(spec, request):
20+
validator = RequestValidator(spec)
21+
result = validator.validate(request)
22+
try:
23+
result.validate()
24+
except OpenAPIBodyError:
25+
return result.parameters
26+
else:
27+
return result.parameters
28+
29+
30+
def validate_body(spec, request):
31+
validator = RequestValidator(spec)
32+
result = validator.validate(request)
33+
try:
34+
result.validate()
35+
except OpenAPIParameterError:
36+
return result.body
37+
else:
38+
return result.body

openapi_core/specs.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from openapi_spec_validator import openapi_v3_spec_validator
77

88
from openapi_core.components import ComponentsFactory
9-
from openapi_core.exceptions import InvalidOperationError
9+
from openapi_core.exceptions import InvalidOperation, InvalidServer
1010
from openapi_core.infos import InfoFactory
1111
from openapi_core.paths import PathsGenerator
1212
from openapi_core.schemas import SchemaRegistry
@@ -32,14 +32,22 @@ def __getitem__(self, path_name):
3232
def default_url(self):
3333
return self.servers[0].default_url
3434

35+
def get_server(self, full_url_pattern):
36+
for spec_server in self.servers:
37+
if spec_server.default_url in full_url_pattern:
38+
return spec_server
39+
40+
raise InvalidServer(
41+
"Invalid request server {0}".format(full_url_pattern))
42+
3543
def get_server_url(self, index=0):
3644
return self.servers[index].default_url
3745

3846
def get_operation(self, path_pattern, http_method):
3947
try:
4048
return self.paths[path_pattern].operations[http_method]
4149
except KeyError:
42-
raise InvalidOperationError(
50+
raise InvalidOperation(
4351
"Unknown operation path {0} with method {1}".format(
4452
path_pattern, http_method))
4553

0 commit comments

Comments
 (0)