String validation

This commit is contained in:
Artur Maciag 2018-08-22 11:51:06 +01:00
parent 101a11ebe9
commit 82f32be1e7
4 changed files with 148 additions and 18 deletions

View file

@ -1,6 +1,7 @@
"""OpenAPI core schemas models module"""
import logging
from collections import defaultdict
from datetime import date, datetime
import warnings
from six import iteritems, integer_types, binary_type, text_type
@ -11,7 +12,9 @@ from openapi_core.schema.schemas.exceptions import (
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema,
)
from openapi_core.schema.schemas.util import forcebool, format_date
from openapi_core.schema.schemas.util import (
forcebool, format_date, format_datetime,
)
from openapi_core.schema.schemas.validators import (
TypeValidator, AttributeValidator,
)
@ -28,16 +31,27 @@ class Schema(object):
SchemaType.BOOLEAN: forcebool,
}
FORMAT_CALLABLE_GETTER = defaultdict(lambda: lambda x: x, {
SchemaFormat.DATE.value: format_date,
})
STRING_FORMAT_CAST_CALLABLE_GETTER = {
SchemaFormat.NONE: text_type,
SchemaFormat.DATE: format_date,
SchemaFormat.DATETIME: format_datetime,
SchemaFormat.BINARY: binary_type,
}
STRING_FORMAT_VALIDATOR_CALLABLE_GETTER = {
SchemaFormat.NONE: TypeValidator(text_type),
SchemaFormat.DATE: TypeValidator(date, exclude=datetime),
SchemaFormat.DATETIME: TypeValidator(datetime),
SchemaFormat.BINARY: TypeValidator(binary_type),
}
TYPE_VALIDATOR_CALLABLE_GETTER = {
None: lambda x: x,
None: lambda x: True,
SchemaType.BOOLEAN: TypeValidator(bool),
SchemaType.INTEGER: TypeValidator(integer_types, exclude=bool),
SchemaType.NUMBER: TypeValidator(integer_types, float, exclude=bool),
SchemaType.STRING: TypeValidator(binary_type, text_type),
SchemaType.STRING: TypeValidator(
text_type, date, datetime, binary_type),
SchemaType.ARRAY: TypeValidator(list, tuple),
SchemaType.OBJECT: AttributeValidator('__dict__'),
}
@ -158,7 +172,16 @@ class Schema(object):
return casted
def _unmarshal_string(self, value):
formatter = self.FORMAT_CALLABLE_GETTER[self.format]
try:
schema_format = SchemaFormat(self.format)
except ValueError:
# @todo: implement custom format unmarshalling support
raise OpenAPISchemaError(
"Unsupported {0} format unmarshalling".format(self.format)
)
else:
formatter = self.STRING_FORMAT_CAST_CALLABLE_GETTER[schema_format]
try:
return formatter(value)
except ValueError:
@ -244,6 +267,7 @@ class Schema(object):
def get_validator_mapping(self):
mapping = {
SchemaType.ARRAY: self._validate_collection,
SchemaType.STRING: self._validate_string,
SchemaType.OBJECT: self._validate_object,
}
@ -277,6 +301,26 @@ class Schema(object):
return list(map(self.items.validate, value))
def _validate_string(self, value):
try:
schema_format = SchemaFormat(self.format)
except ValueError:
# @todo: implement custom format validation support
raise OpenAPISchemaError(
"Unsupported {0} format validation".format(self.format)
)
else:
format_validator_callable =\
self.STRING_FORMAT_VALIDATOR_CALLABLE_GETTER[schema_format]
if not format_validator_callable(value):
raise InvalidSchemaValue(
"Value of {0} not valid format of {1}".format(
value, self.format)
)
return True
def _validate_object(self, value):
properties = value.__dict__

View file

@ -18,3 +18,7 @@ def dicthash(d):
def format_date(value):
return datetime.datetime.strptime(value, '%Y-%m-%d').date()
def format_datetime(value):
return datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S')

View file

@ -286,7 +286,6 @@ components:
properties:
name:
type: string
format: custom
TagList:
type: array
items:

View file

@ -9,6 +9,8 @@ from openapi_core.schema.schemas.exceptions import (
)
from openapi_core.schema.schemas.models import Schema
from six import b, u
class TestSchemaIteritems(object):
@ -77,13 +79,22 @@ class TestSchemaUnmarshal(object):
assert result == datetime.date(2018, 1, 2)
def test_string_format_datetime(self):
schema = Schema('string', schema_format='date-time')
value = '2018-01-02T00:00:00'
result = schema.unmarshal(value)
assert result == datetime.datetime(2018, 1, 2, 0, 0, 0)
@pytest.mark.xfail(reason="No custom formats support atm")
def test_string_format_custom(self):
custom_format = 'custom'
schema = Schema('string', schema_format=custom_format)
value = 'x'
with mock.patch.dict(
Schema.FORMAT_CALLABLE_GETTER,
Schema.STRING_FORMAT_CAST_CALLABLE_GETTER,
{custom_format: lambda x: x + '-custom'},
):
result = schema.unmarshal(value)
@ -95,17 +106,17 @@ class TestSchemaUnmarshal(object):
schema = Schema('string', schema_format=unknown_format)
value = 'x'
result = schema.unmarshal(value)
assert result == 'x'
with pytest.raises(OpenAPISchemaError):
schema.unmarshal(value)
@pytest.mark.xfail(reason="No custom formats support atm")
def test_string_format_invalid_value(self):
custom_format = 'custom'
schema = Schema('string', schema_format=custom_format)
value = 'x'
with mock.patch.dict(
Schema.FORMAT_CALLABLE_GETTER,
Schema.STRING_FORMAT_CAST_CALLABLE_GETTER,
{custom_format: mock.Mock(side_effect=ValueError())},
), pytest.raises(
InvalidSchemaValue, message='Failed to format value'
@ -191,7 +202,7 @@ class TestSchemaValidate(object):
assert result == value
@pytest.mark.parametrize('value', [1, 3.14, 'true', [True, False]])
@pytest.mark.parametrize('value', [1, 3.14, u('true'), [True, False]])
def test_boolean_invalid(self, value):
schema = Schema('boolean')
@ -213,7 +224,7 @@ class TestSchemaValidate(object):
assert result == value
@pytest.mark.parametrize('value', [False, 1, 3.14, 'true'])
@pytest.mark.parametrize('value', [False, 1, 3.14, u('true')])
def test_array_invalid(self, value):
schema = Schema('array')
@ -228,7 +239,7 @@ class TestSchemaValidate(object):
assert result == value
@pytest.mark.parametrize('value', [False, 3.14, 'true', [1, 2]])
@pytest.mark.parametrize('value', [False, 3.14, u('true'), [1, 2]])
def test_integer_invalid(self, value):
schema = Schema('integer')
@ -250,7 +261,7 @@ class TestSchemaValidate(object):
with pytest.raises(InvalidSchemaValue):
schema.validate(value)
@pytest.mark.parametrize('value', ['true', b'true'])
@pytest.mark.parametrize('value', [u('true'), ])
def test_string(self, value):
schema = Schema('string')
@ -258,13 +269,85 @@ class TestSchemaValidate(object):
assert result == value
@pytest.mark.parametrize('value', [False, 1, 3.14, [1, 3]])
@pytest.mark.parametrize('value', [b('test'), False, 1, 3.14, [1, 3]])
def test_string_invalid(self, value):
schema = Schema('string')
with pytest.raises(InvalidSchemaValue):
schema.validate(value)
@pytest.mark.parametrize('value', [
b('true'), u('test'), False, 1, 3.14, [1, 3],
datetime.datetime(1989, 1, 2),
])
def test_string_format_date_invalid(self, value):
schema = Schema('string', schema_format='date')
with pytest.raises(InvalidSchemaValue):
schema.validate(value)
@pytest.mark.parametrize('value', [
datetime.date(1989, 1, 2), datetime.date(2018, 1, 2),
])
def test_string_format_date(self, value):
schema = Schema('string', schema_format='date')
result = schema.validate(value)
assert result == value
@pytest.mark.parametrize('value', [
b('true'), u('true'), False, 1, 3.14, [1, 3],
datetime.date(1989, 1, 2),
])
def test_string_format_datetime_invalid(self, value):
schema = Schema('string', schema_format='date-time')
with pytest.raises(InvalidSchemaValue):
schema.validate(value)
@pytest.mark.parametrize('value', [
datetime.datetime(1989, 1, 2, 0, 0, 0),
datetime.datetime(2018, 1, 2, 23, 59, 59),
])
def test_string_format_datetime(self, value):
schema = Schema('string', schema_format='date-time')
result = schema.validate(value)
assert result == value
@pytest.mark.parametrize('value', [
u('true'), False, 1, 3.14, [1, 3], datetime.date(1989, 1, 2),
datetime.datetime(1989, 1, 2, 0, 0, 0),
])
def test_string_format_binary_invalid(self, value):
schema = Schema('string', schema_format='binary')
with pytest.raises(InvalidSchemaValue):
schema.validate(value)
@pytest.mark.parametrize('value', [
b('stream'), b('text'),
])
def test_string_format_binary(self, value):
schema = Schema('string', schema_format='binary')
result = schema.validate(value)
assert result == value
@pytest.mark.parametrize('value', [
u('test'), b('stream'), datetime.date(1989, 1, 2),
datetime.datetime(1989, 1, 2, 0, 0, 0),
])
def test_string_format_unknown(self, value):
unknown_format = 'unknown'
schema = Schema('string', schema_format=unknown_format)
with pytest.raises(OpenAPISchemaError):
schema.validate(value)
@pytest.mark.parametrize('value', ['true', False, 1, 3.14, [1, 3]])
def test_object_not_an_object(self, value):
schema = Schema('object')