Skip to content

Commit 17855ae

Browse files
authored
Merge pull request #81 from p1c2u/feature/separate-casting-and-validation
Separate schema casting and validation
2 parents 7aaa517 + f9a7472 commit 17855ae

File tree

6 files changed

+170
-3
lines changed

6 files changed

+170
-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, **options):
4+
self.types = types
5+
self.exclude = options.get('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: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,102 @@ 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('schema_type', [
163+
'boolean', 'array', 'integer', 'number', 'string',
164+
])
165+
def test_null(self, schema_type):
166+
schema = Schema(schema_type)
167+
value = None
168+
169+
with pytest.raises(InvalidSchemaValue):
170+
schema.validate(value)
171+
172+
@pytest.mark.parametrize('schema_type', [
173+
'boolean', 'array', 'integer', 'number', 'string',
174+
])
175+
def test_nullable(self, schema_type):
176+
schema = Schema(schema_type, nullable=True)
177+
value = None
178+
179+
result = schema.validate(value)
180+
181+
assert result is None
182+
183+
@pytest.mark.parametrize('value', [False, True])
184+
def test_boolean(self, value):
185+
schema = Schema('boolean')
186+
187+
result = schema.validate(value)
188+
189+
assert result == value
190+
191+
@pytest.mark.parametrize('value', [1, 3.14, 'true', [True, False]])
192+
def test_boolean_invalid(self, value):
193+
schema = Schema('boolean')
194+
195+
with pytest.raises(InvalidSchemaValue):
196+
schema.validate(value)
197+
198+
@pytest.mark.parametrize('value', [[1, 2], (3, 4)])
199+
def test_array(self, value):
200+
schema = Schema('array')
201+
202+
result = schema.validate(value)
203+
204+
assert result == value
205+
206+
@pytest.mark.parametrize('value', [False, 1, 3.14, 'true'])
207+
def test_array_invalid(self, value):
208+
schema = Schema('array')
209+
210+
with pytest.raises(InvalidSchemaValue):
211+
schema.validate(value)
212+
213+
@pytest.mark.parametrize('value', [1, 3])
214+
def test_integer(self, value):
215+
schema = Schema('integer')
216+
217+
result = schema.validate(value)
218+
219+
assert result == value
220+
221+
@pytest.mark.parametrize('value', [False, 3.14, 'true', [1, 2]])
222+
def test_integer_invalid(self, value):
223+
schema = Schema('integer')
224+
225+
with pytest.raises(InvalidSchemaValue):
226+
schema.validate(value)
227+
228+
@pytest.mark.parametrize('value', [1, 3.14])
229+
def test_number(self, value):
230+
schema = Schema('number')
231+
232+
result = schema.validate(value)
233+
234+
assert result == value
235+
236+
@pytest.mark.parametrize('value', [False, 'true', [1, 3]])
237+
def test_number_invalid(self, value):
238+
schema = Schema('number')
239+
240+
with pytest.raises(InvalidSchemaValue):
241+
schema.validate(value)
242+
243+
@pytest.mark.parametrize('value', ['true', b'true'])
244+
def test_string(self, value):
245+
schema = Schema('string')
246+
247+
result = schema.validate(value)
248+
249+
assert result == value
250+
251+
@pytest.mark.parametrize('value', [False, 1, 3.14, [1, 3]])
252+
def test_string_invalid(self, value):
253+
schema = Schema('string')
254+
255+
with pytest.raises(InvalidSchemaValue):
256+
schema.validate(value)

0 commit comments

Comments
 (0)