diff --git a/openapi_core/schema/schemas/exceptions.py b/openapi_core/schema/schemas/exceptions.py index 2adcde5..4c6cac1 100644 --- a/openapi_core/schema/schemas/exceptions.py +++ b/openapi_core/schema/schemas/exceptions.py @@ -7,6 +7,27 @@ class OpenAPISchemaError(OpenAPIMappingError): pass +class UnmarshallError(OpenAPISchemaError): + """Unmarshall operation error""" + pass + + +@attr.s(hash=True) +class UnmarshallValueError(UnmarshallError): + """Failed to unmarshal value to type""" + value = attr.ib() + type = attr.ib() + original_exception = attr.ib(default=None) + + def __str__(self): + return ( + "Failed to unmarshal value {value} to type {type}: {exception}" + ).format( + value=self.value, type=self.type, + exception=self.original_exception, + ) + + @attr.s(hash=True) class NoValidSchema(OpenAPISchemaError): value = attr.ib() @@ -33,20 +54,13 @@ class InvalidSchemaValue(OpenAPISchemaError): return self.msg.format(value=self.value, type=self.type) -@attr.s(hash=True) -class InvalidCustomFormatSchemaValue(InvalidSchemaValue): - original_exception = attr.ib() - - def __str__(self): - return self.msg.format(value=self.value, type=self.type, exception=self.original_exception) - - @attr.s(hash=True) class UndefinedSchemaProperty(OpenAPISchemaError): extra_props = attr.ib() def __str__(self): - return "Extra unexpected properties found in schema: {0}".format(self.extra_props) + return "Extra unexpected properties found in schema: {0}".format( + self.extra_props) @attr.s(hash=True) @@ -55,7 +69,8 @@ class InvalidSchemaProperty(OpenAPISchemaError): original_exception = attr.ib() def __str__(self): - return "Invalid schema property {0}: {1}".format(self.property_name, self.original_exception) + return "Invalid schema property {0}: {1}".format( + self.property_name, self.original_exception) @attr.s(hash=True) @@ -66,14 +81,46 @@ class MissingSchemaProperty(OpenAPISchemaError): return "Missing schema property: {0}".format(self.property_name) -class UnmarshallerError(OpenAPIMappingError): +class UnmarshallerError(UnmarshallError): + """Unmarshaller error""" pass +@attr.s(hash=True) +class InvalidCustomFormatSchemaValue(UnmarshallerError): + """Value failed to format with custom formatter""" + value = attr.ib() + type = attr.ib() + original_exception = attr.ib() + + def __str__(self): + return ( + "Failed to format value {value} to format {type}: {exception}" + ).format( + value=self.value, type=self.type, + exception=self.original_exception, + ) + + +@attr.s(hash=True) +class FormatterNotFoundError(UnmarshallerError): + """Formatter not found to unmarshal""" + value = attr.ib() + type_format = attr.ib() + + def __str__(self): + return ( + "Formatter not found for {format} format " + "to unmarshal value {value}" + ).format(format=self.type_format, value=self.value) + + +@attr.s(hash=True) class UnmarshallerStrictTypeError(UnmarshallerError): value = attr.ib() types = attr.ib() def __str__(self): - return "Value {value} is not one of types {types}".format( - self.value, self.types) + types = ', '.join(list(map(str, self.types))) + return "Value {value} is not one of types: {types}".format( + value=self.value, types=types) diff --git a/openapi_core/schema/schemas/models.py b/openapi_core/schema/schemas/models.py index 44ca3e6..e85af68 100644 --- a/openapi_core/schema/schemas/models.py +++ b/openapi_core/schema/schemas/models.py @@ -17,8 +17,8 @@ from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType from openapi_core.schema.schemas.exceptions import ( InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty, OpenAPISchemaError, NoValidSchema, - UndefinedItemsSchema, InvalidCustomFormatSchemaValue, InvalidSchemaProperty, - UnmarshallerStrictTypeError, + UndefinedItemsSchema, InvalidSchemaProperty, + UnmarshallerError, UnmarshallValueError, UnmarshallError, ) from openapi_core.schema.schemas.util import ( forcebool, format_date, format_datetime, format_byte, format_uuid, @@ -212,12 +212,12 @@ class Schema(object): warnings.warn("The schema is deprecated", DeprecationWarning) if value is None: if not self.nullable: - raise InvalidSchemaValue("Null value for non-nullable schema", value, self.type) + raise UnmarshallError( + "Null value for non-nullable schema", value, self.type) return self.default if self.enum and value not in self.enum: - raise InvalidSchemaValue( - "Value {value} not in enum choices: {type}", value, self.enum) + raise UnmarshallError("Invalid value for enum: {0}".format(value)) unmarshal_mapping = self.get_unmarshal_mapping( custom_formatters=custom_formatters, strict=strict) @@ -228,12 +228,8 @@ class Schema(object): unmarshal_callable = unmarshal_mapping[self.type] try: unmarshalled = unmarshal_callable(value) - except UnmarshallerStrictTypeError: - raise InvalidSchemaValue( - "Value {value} is not of type {type}", value, self.type) - except ValueError: - raise InvalidSchemaValue( - "Failed to unmarshal value {value} to type {type}", value, self.type) + except ValueError as exc: + raise UnmarshallValueError(value, self.type, exc) return unmarshalled @@ -268,7 +264,7 @@ class Schema(object): for subschema in self.one_of: try: unmarshalled = subschema.unmarshal(value, custom_formatters) - except (OpenAPISchemaError, TypeError, ValueError): + except UnmarshallError: continue else: if result is not None: @@ -285,9 +281,7 @@ class Schema(object): unmarshal_callable = unmarshal_mapping[schema_type] try: return unmarshal_callable(value) - except UnmarshallerStrictTypeError: - continue - except (OpenAPISchemaError, TypeError): + except (UnmarshallError, ValueError): continue log.warning("failed to unmarshal any type") @@ -295,7 +289,7 @@ class Schema(object): def _unmarshal_collection(self, value, custom_formatters=None, strict=True): if not isinstance(value, (list, tuple)): - raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type) + raise ValueError("Invalid value for collection: {0}".format(value)) f = functools.partial( self.items.unmarshal, @@ -306,7 +300,7 @@ class Schema(object): def _unmarshal_object(self, value, model_factory=None, custom_formatters=None, strict=True): if not isinstance(value, (dict, )): - raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type) + raise ValueError("Invalid value for object: {0}".format(value)) model_factory = model_factory or ModelFactory() diff --git a/openapi_core/schema/schemas/unmarshallers.py b/openapi_core/schema/schemas/unmarshallers.py index 31ba5b6..7723551 100644 --- a/openapi_core/schema/schemas/unmarshallers.py +++ b/openapi_core/schema/schemas/unmarshallers.py @@ -6,6 +6,7 @@ from openapi_core.schema.schemas.exceptions import ( OpenAPISchemaError, InvalidSchemaProperty, UnmarshallerStrictTypeError, + FormatterNotFoundError, ) from openapi_core.schema.schemas.util import ( forcebool, format_date, format_datetime, format_byte, format_uuid, @@ -49,17 +50,12 @@ class PrimitiveTypeUnmarshaller(StrictUnmarshaller): formatter = formatters.get(schema_format) if formatter is None: - raise InvalidSchemaValue( - "Unsupported format {type} unmarshalling " - "for value {value}", - value, type_format) + raise FormatterNotFoundError(value, type_format) try: return formatter(value) except ValueError as exc: - raise InvalidCustomFormatSchemaValue( - "Failed to format value {value} to format {type}: {exception}", - value, type_format, exc) + raise InvalidCustomFormatSchemaValue(value, type_format, exc) def get_formatters(self): return self.FORMATTERS diff --git a/tests/unit/schema/test_schemas.py b/tests/unit/schema/test_schemas.py index f8d9a88..49c5019 100644 --- a/tests/unit/schema/test_schemas.py +++ b/tests/unit/schema/test_schemas.py @@ -7,7 +7,9 @@ import pytest from openapi_core.extensions.models.models import Model from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType from openapi_core.schema.schemas.exceptions import ( - InvalidSchemaValue, OpenAPISchemaError, + InvalidSchemaValue, OpenAPISchemaError, UnmarshallerStrictTypeError, + UnmarshallValueError, UnmarshallError, InvalidCustomFormatSchemaValue, + FormatterNotFoundError, ) from openapi_core.schema.schemas.models import Schema @@ -88,14 +90,14 @@ class TestSchemaUnmarshal(object): schema = Schema('string') value = 1.23 - with pytest.raises(InvalidSchemaValue): + with pytest.raises(UnmarshallerStrictTypeError): schema.unmarshal(value) def test_string_none(self): schema = Schema('string') value = None - with pytest.raises(InvalidSchemaValue): + with pytest.raises(UnmarshallError): schema.unmarshal(value) def test_string_default(self): @@ -103,7 +105,7 @@ class TestSchemaUnmarshal(object): schema = Schema('string', default=default_value) value = None - with pytest.raises(InvalidSchemaValue): + with pytest.raises(UnmarshallError): schema.unmarshal(value) def test_string_default_nullable(self): @@ -150,7 +152,7 @@ class TestSchemaUnmarshal(object): schema = Schema('string', schema_format=custom_format) value = 'x' - with pytest.raises(InvalidSchemaValue): + with pytest.raises(InvalidCustomFormatSchemaValue): schema.unmarshal( value, custom_formatters={custom_format: custom_formatter}) @@ -168,7 +170,10 @@ class TestSchemaUnmarshal(object): value = 'x' with pytest.raises( - InvalidSchemaValue, message='Failed to format value' + FormatterNotFoundError, + message=( + 'Formatter not found for custom format to unmarshal value x' + ), ): schema.unmarshal(value) @@ -184,14 +189,14 @@ class TestSchemaUnmarshal(object): schema = Schema('integer') value = '123' - with pytest.raises(InvalidSchemaValue): + with pytest.raises(UnmarshallerStrictTypeError): schema.unmarshal(value) def test_integer_enum_invalid(self): schema = Schema('integer', enum=[1, 2, 3]) value = '123' - with pytest.raises(InvalidSchemaValue): + with pytest.raises(UnmarshallError): schema.unmarshal(value) def test_integer_enum(self): @@ -206,7 +211,7 @@ class TestSchemaUnmarshal(object): schema = Schema('integer', enum=[1, 2, 3]) value = '2' - with pytest.raises(InvalidSchemaValue): + with pytest.raises(UnmarshallError): schema.unmarshal(value) def test_integer_default(self): @@ -214,7 +219,7 @@ class TestSchemaUnmarshal(object): schema = Schema('integer', default=default_value) value = None - with pytest.raises(InvalidSchemaValue): + with pytest.raises(UnmarshallError): schema.unmarshal(value) def test_integer_default_nullable(self): @@ -230,7 +235,7 @@ class TestSchemaUnmarshal(object): schema = Schema('integer') value = 'abc' - with pytest.raises(InvalidSchemaValue): + with pytest.raises(UnmarshallerStrictTypeError): schema.unmarshal(value) def test_array_valid(self): @@ -245,14 +250,14 @@ class TestSchemaUnmarshal(object): schema = Schema('array', items=Schema('string')) value = '123' - with pytest.raises(InvalidSchemaValue): + with pytest.raises(UnmarshallValueError): schema.unmarshal(value) def test_array_of_integer_string_invalid(self): schema = Schema('array', items=Schema('integer')) value = '123' - with pytest.raises(InvalidSchemaValue): + with pytest.raises(UnmarshallValueError): schema.unmarshal(value) def test_boolean_valid(self): @@ -267,7 +272,7 @@ class TestSchemaUnmarshal(object): schema = Schema('boolean') value = 'True' - with pytest.raises(InvalidSchemaValue): + with pytest.raises(UnmarshallerStrictTypeError): schema.unmarshal(value) def test_number_valid(self): @@ -282,7 +287,7 @@ class TestSchemaUnmarshal(object): schema = Schema('number') value = '1.23' - with pytest.raises(InvalidSchemaValue): + with pytest.raises(UnmarshallerStrictTypeError): schema.unmarshal(value) def test_number_int(self):