diff --git a/openapi_core/schema/schemas/models.py b/openapi_core/schema/schemas/models.py index 7353dd0c..c738cc1d 100644 --- a/openapi_core/schema/schemas/models.py +++ b/openapi_core/schema/schemas/models.py @@ -20,6 +20,7 @@ ) from openapi_core.schema.schemas.util import ( forcebool, format_date, format_datetime, format_byte, format_uuid, + format_number, ) from openapi_core.schema.schemas.validators import ( TypeValidator, AttributeValidator, @@ -50,11 +51,19 @@ class Schema(object): SchemaFormat.BYTE: Format(format_byte, TypeValidator(text_type)), } + NUMBER_FORMAT_CALLABLE_GETTER = { + SchemaFormat.NONE: Format(format_number, TypeValidator( + integer_types + (float, ), exclude=bool)), + SchemaFormat.FLOAT: Format(float, TypeValidator(float)), + SchemaFormat.DOUBLE: Format(float, TypeValidator(float)), + } + TYPE_VALIDATOR_CALLABLE_GETTER = { SchemaType.ANY: lambda x: True, SchemaType.BOOLEAN: TypeValidator(bool), SchemaType.INTEGER: TypeValidator(integer_types, exclude=bool), - SchemaType.NUMBER: TypeValidator(integer_types, float, exclude=bool), + SchemaType.NUMBER: TypeValidator( + integer_types + (float, ), exclude=bool), SchemaType.STRING: TypeValidator( text_type, date, datetime, binary_type, UUID), SchemaType.ARRAY: TypeValidator(list, tuple), @@ -228,16 +237,33 @@ def _unmarshal_string(self, value, custom_formatters=None, strict=True): "Failed to format value {value} to format {type}: {exception}", value, self.format, exc) def _unmarshal_integer(self, value, custom_formatters=None, strict=True): - if strict and not isinstance(value, (integer_types, )): + if strict and not isinstance(value, integer_types): raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type) return int(value) def _unmarshal_number(self, value, custom_formatters=None, strict=True): - if strict and not isinstance(value, (float, )): + if strict and not isinstance(value, (float, ) + integer_types): raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type) - return float(value) + try: + schema_format = SchemaFormat(self.format) + except ValueError: + msg = "Unsupported format {type} unmarshalling for value {value}" + if custom_formatters is not None: + formatnumber = custom_formatters.get(self.format) + if formatnumber is None: + raise InvalidSchemaValue(msg, value, self.format) + else: + raise InvalidSchemaValue(msg, value, self.format) + else: + formatnumber = self.NUMBER_FORMAT_CALLABLE_GETTER[schema_format] + + try: + return formatnumber.unmarshal(value) + except ValueError as exc: + raise InvalidCustomFormatSchemaValue( + "Failed to format value {value} to format {type}: {exception}", value, self.format, exc) def _unmarshal_boolean(self, value, custom_formatters=None, strict=True): if strict and not isinstance(value, (bool, )): diff --git a/openapi_core/schema/schemas/util.py b/openapi_core/schema/schemas/util.py index 9cfab42a..528b8fbc 100644 --- a/openapi_core/schema/schemas/util.py +++ b/openapi_core/schema/schemas/util.py @@ -3,7 +3,7 @@ import datetime from distutils.util import strtobool from json import dumps -from six import string_types, text_type +from six import string_types, text_type, integer_types import strict_rfc3339 from uuid import UUID @@ -36,3 +36,10 @@ def format_uuid(value): def format_byte(value, encoding='utf8'): return text_type(b64decode(value), encoding) + + +def format_number(value): + if isinstance(value, integer_types + (float, )): + return value + + return float(value) diff --git a/tests/unit/schema/test_schemas.py b/tests/unit/schema/test_schemas.py index b66dad7d..eaf5e32f 100644 --- a/tests/unit/schema/test_schemas.py +++ b/tests/unit/schema/test_schemas.py @@ -262,12 +262,35 @@ def test_number_string_invalid(self): with pytest.raises(InvalidSchemaValue): schema.unmarshal(value) - def test_number_int_invalid(self): + def test_number_int(self): schema = Schema('number') value = 1 + result = schema.unmarshal(value) - with pytest.raises(InvalidSchemaValue): - schema.unmarshal(value) + assert result == 1 + assert type(result) == int + + def test_number_float(self): + schema = Schema('number') + value = 1.2 + result = schema.unmarshal(value) + + assert result == 1.2 + assert type(result) == float + + def test_number_format_float(self): + schema = Schema('number', schema_format='float') + value = 1.2 + result = schema.unmarshal(value) + + assert result == 1.2 + + def test_number_format_double(self): + schema = Schema('number', schema_format='double') + value = 1.2 + result = schema.unmarshal(value) + + assert result == 1.2 class TestSchemaValidate(object):