Skip to content

Commit 7ef22c9

Browse files
committed
Separate schema casting and validation
1 parent 7aaa517 commit 7ef22c9

File tree

6 files changed

+149
-3
lines changed

6 files changed

+149
-3
lines changed

openapi_core/schema/media_types/models.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ def unmarshal(self, value):
4242
raise InvalidMediaTypeValue(str(exc))
4343

4444
try:
45-
return self.schema.unmarshal(deserialized)
45+
unmarshalled = self.schema.unmarshal(deserialized)
46+
except InvalidSchemaValue as exc:
47+
raise InvalidMediaTypeValue(str(exc))
48+
49+
try:
50+
return self.schema.validate(unmarshalled)
4651
except InvalidSchemaValue as exc:
4752
raise InvalidMediaTypeValue(str(exc))

openapi_core/schema/parameters/models.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ def unmarshal(self, value):
112112
raise InvalidParameterValue(str(exc))
113113

114114
try:
115-
return self.schema.unmarshal(deserialized)
115+
unmarshalled = self.schema.unmarshal(deserialized)
116+
except InvalidSchemaValue as exc:
117+
raise InvalidParameterValue(str(exc))
118+
119+
try:
120+
return self.schema.validate(unmarshalled)
116121
except InvalidSchemaValue as exc:
117122
raise InvalidParameterValue(str(exc))

openapi_core/schema/responses/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ def __init__(
1717
self.links = links and dict(links) or {}
1818

1919
def __getitem__(self, mimetype):
20+
return self.get_content_type(mimetype)
21+
22+
def get_content_type(self, mimetype):
2023
try:
2124
return self.content[mimetype]
2225
except MimeTypeNotFound:

openapi_core/schema/schemas/models.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from collections import defaultdict
44
import warnings
55

6-
from six import iteritems
6+
from six import iteritems, integer_types, binary_type, text_type
77

88
from openapi_core.extensions.models.factories import ModelFactory
99
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
@@ -12,6 +12,9 @@
1212
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema,
1313
)
1414
from openapi_core.schema.schemas.util import forcebool, format_date
15+
from openapi_core.schema.schemas.validators import (
16+
TypeValidator, AttributeValidator,
17+
)
1518

1619
log = logging.getLogger(__name__)
1720

@@ -29,6 +32,16 @@ class Schema(object):
2932
SchemaFormat.DATE.value: format_date,
3033
})
3134

35+
VALIDATOR_CALLABLE_GETTER = {
36+
None: lambda x: x,
37+
SchemaType.BOOLEAN: TypeValidator(bool),
38+
SchemaType.INTEGER: TypeValidator(integer_types, exclude=bool),
39+
SchemaType.NUMBER: TypeValidator(integer_types, float, exclude=bool),
40+
SchemaType.STRING: TypeValidator(binary_type, text_type),
41+
SchemaType.ARRAY: TypeValidator(list, tuple),
42+
SchemaType.OBJECT: AttributeValidator('__class__'),
43+
}
44+
3245
def __init__(
3346
self, schema_type=None, model=None, properties=None, items=None,
3447
schema_format=None, required=None, default=None, nullable=False,
@@ -222,3 +235,19 @@ def _unmarshal_properties(self, value, one_of_schema=None):
222235
prop_value = prop.default
223236
properties[prop_name] = prop.unmarshal(prop_value)
224237
return properties
238+
239+
def validate(self, value):
240+
if value is None:
241+
if not self.nullable:
242+
raise InvalidSchemaValue("Null value for non-nullable schema")
243+
return self.default
244+
245+
validator = self.VALIDATOR_CALLABLE_GETTER[self.type]
246+
247+
if not validator(value):
248+
raise InvalidSchemaValue(
249+
"Value of {0} not valid type of {1}".format(
250+
value, self.type.value)
251+
)
252+
253+
return value
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
class TypeValidator(object):
2+
3+
def __init__(self, *types, exclude=None):
4+
self.types = types
5+
self.exclude = exclude
6+
7+
def __call__(self, value):
8+
if self.exclude is not None and isinstance(value, self.exclude):
9+
return False
10+
11+
if not isinstance(value, self.types):
12+
return False
13+
14+
return True
15+
16+
17+
class AttributeValidator(object):
18+
19+
def __init__(self, attribute):
20+
self.attribute = attribute
21+
22+
def __call__(self, value):
23+
if not hasattr(value, self.attribute):
24+
return False
25+
26+
return True

tests/unit/schema/test_schemas.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,81 @@ def test_integer_invalid(self):
155155

156156
with pytest.raises(InvalidSchemaValue):
157157
schema.unmarshal(value)
158+
159+
160+
class TestSchemaValidate(object):
161+
162+
@pytest.mark.parametrize('value', [False, True])
163+
def test_boolean(self, value):
164+
schema = Schema('boolean')
165+
166+
result = schema.validate(value)
167+
168+
assert result == value
169+
170+
@pytest.mark.parametrize('value', [1, 3.14, 'true', [True, False]])
171+
def test_boolean_invalid(self, value):
172+
schema = Schema('boolean')
173+
174+
with pytest.raises(InvalidSchemaValue):
175+
schema.validate(value)
176+
177+
@pytest.mark.parametrize('value', [[1, 2], (3, 4)])
178+
def test_array(self, value):
179+
schema = Schema('array')
180+
181+
result = schema.validate(value)
182+
183+
assert result == value
184+
185+
@pytest.mark.parametrize('value', [False, 1, 3.14, 'true'])
186+
def test_array_invalid(self, value):
187+
schema = Schema('array')
188+
189+
with pytest.raises(InvalidSchemaValue):
190+
schema.validate(value)
191+
192+
@pytest.mark.parametrize('value', [1, 3])
193+
def test_integer(self, value):
194+
schema = Schema('integer')
195+
196+
result = schema.validate(value)
197+
198+
assert result == value
199+
200+
@pytest.mark.parametrize('value', [False, 3.14, 'true', [1, 2]])
201+
def test_integer_invalid(self, value):
202+
schema = Schema('integer')
203+
204+
with pytest.raises(InvalidSchemaValue):
205+
schema.validate(value)
206+
207+
@pytest.mark.parametrize('value', [1, 3.14])
208+
def test_number(self, value):
209+
schema = Schema('number')
210+
211+
result = schema.validate(value)
212+
213+
assert result == value
214+
215+
@pytest.mark.parametrize('value', [False, 'true', [1, 3]])
216+
def test_number_invalid(self, value):
217+
schema = Schema('number')
218+
219+
with pytest.raises(InvalidSchemaValue):
220+
schema.validate(value)
221+
222+
@pytest.mark.parametrize('value', ['true', b'true'])
223+
def test_string(self, value):
224+
schema = Schema('string')
225+
226+
result = schema.validate(value)
227+
228+
assert result == value
229+
230+
@pytest.mark.parametrize('value', [False, 1, 3.14, [1, 3]])
231+
def test_string_invalid(self, value):
232+
schema = Schema('string')
233+
234+
with pytest.raises(InvalidSchemaValue):
235+
schema.validate(value)

0 commit comments

Comments
 (0)