mirror of
https://github.com/correl/openapi-core.git
synced 2024-12-28 19:19:23 +00:00
Merge pull request #86 from p1c2u/feature/string-validation
String validation
This commit is contained in:
commit
4731504f32
4 changed files with 148 additions and 18 deletions
|
@ -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__
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -286,7 +286,6 @@ components:
|
|||
properties:
|
||||
name:
|
||||
type: string
|
||||
format: custom
|
||||
TagList:
|
||||
type: array
|
||||
items:
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in a new issue