mirror of
https://github.com/correl/openapi-core.git
synced 2025-01-01 11:03:19 +00:00
Schema unmarshal exceptions refactor
This commit is contained in:
parent
fd99117278
commit
939cec94e7
4 changed files with 94 additions and 52 deletions
|
@ -7,6 +7,27 @@ class OpenAPISchemaError(OpenAPIMappingError):
|
||||||
pass
|
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)
|
@attr.s(hash=True)
|
||||||
class NoValidSchema(OpenAPISchemaError):
|
class NoValidSchema(OpenAPISchemaError):
|
||||||
value = attr.ib()
|
value = attr.ib()
|
||||||
|
@ -33,20 +54,13 @@ class InvalidSchemaValue(OpenAPISchemaError):
|
||||||
return self.msg.format(value=self.value, type=self.type)
|
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)
|
@attr.s(hash=True)
|
||||||
class UndefinedSchemaProperty(OpenAPISchemaError):
|
class UndefinedSchemaProperty(OpenAPISchemaError):
|
||||||
extra_props = attr.ib()
|
extra_props = attr.ib()
|
||||||
|
|
||||||
def __str__(self):
|
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)
|
@attr.s(hash=True)
|
||||||
|
@ -55,7 +69,8 @@ class InvalidSchemaProperty(OpenAPISchemaError):
|
||||||
original_exception = attr.ib()
|
original_exception = attr.ib()
|
||||||
|
|
||||||
def __str__(self):
|
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)
|
@attr.s(hash=True)
|
||||||
|
@ -66,14 +81,46 @@ class MissingSchemaProperty(OpenAPISchemaError):
|
||||||
return "Missing schema property: {0}".format(self.property_name)
|
return "Missing schema property: {0}".format(self.property_name)
|
||||||
|
|
||||||
|
|
||||||
class UnmarshallerError(OpenAPIMappingError):
|
class UnmarshallerError(UnmarshallError):
|
||||||
|
"""Unmarshaller error"""
|
||||||
pass
|
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):
|
class UnmarshallerStrictTypeError(UnmarshallerError):
|
||||||
value = attr.ib()
|
value = attr.ib()
|
||||||
types = attr.ib()
|
types = attr.ib()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Value {value} is not one of types {types}".format(
|
types = ', '.join(list(map(str, self.types)))
|
||||||
self.value, self.types)
|
return "Value {value} is not one of types: {types}".format(
|
||||||
|
value=self.value, types=types)
|
||||||
|
|
|
@ -17,8 +17,8 @@ from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
|
||||||
from openapi_core.schema.schemas.exceptions import (
|
from openapi_core.schema.schemas.exceptions import (
|
||||||
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
|
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
|
||||||
OpenAPISchemaError, NoValidSchema,
|
OpenAPISchemaError, NoValidSchema,
|
||||||
UndefinedItemsSchema, InvalidCustomFormatSchemaValue, InvalidSchemaProperty,
|
UndefinedItemsSchema, InvalidSchemaProperty,
|
||||||
UnmarshallerStrictTypeError,
|
UnmarshallerError, UnmarshallValueError, UnmarshallError,
|
||||||
)
|
)
|
||||||
from openapi_core.schema.schemas.util import (
|
from openapi_core.schema.schemas.util import (
|
||||||
forcebool, format_date, format_datetime, format_byte, format_uuid,
|
forcebool, format_date, format_datetime, format_byte, format_uuid,
|
||||||
|
@ -212,12 +212,12 @@ class Schema(object):
|
||||||
warnings.warn("The schema is deprecated", DeprecationWarning)
|
warnings.warn("The schema is deprecated", DeprecationWarning)
|
||||||
if value is None:
|
if value is None:
|
||||||
if not self.nullable:
|
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
|
return self.default
|
||||||
|
|
||||||
if self.enum and value not in self.enum:
|
if self.enum and value not in self.enum:
|
||||||
raise InvalidSchemaValue(
|
raise UnmarshallError("Invalid value for enum: {0}".format(value))
|
||||||
"Value {value} not in enum choices: {type}", value, self.enum)
|
|
||||||
|
|
||||||
unmarshal_mapping = self.get_unmarshal_mapping(
|
unmarshal_mapping = self.get_unmarshal_mapping(
|
||||||
custom_formatters=custom_formatters, strict=strict)
|
custom_formatters=custom_formatters, strict=strict)
|
||||||
|
@ -228,12 +228,8 @@ class Schema(object):
|
||||||
unmarshal_callable = unmarshal_mapping[self.type]
|
unmarshal_callable = unmarshal_mapping[self.type]
|
||||||
try:
|
try:
|
||||||
unmarshalled = unmarshal_callable(value)
|
unmarshalled = unmarshal_callable(value)
|
||||||
except UnmarshallerStrictTypeError:
|
except ValueError as exc:
|
||||||
raise InvalidSchemaValue(
|
raise UnmarshallValueError(value, self.type, exc)
|
||||||
"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)
|
|
||||||
|
|
||||||
return unmarshalled
|
return unmarshalled
|
||||||
|
|
||||||
|
@ -268,7 +264,7 @@ class Schema(object):
|
||||||
for subschema in self.one_of:
|
for subschema in self.one_of:
|
||||||
try:
|
try:
|
||||||
unmarshalled = subschema.unmarshal(value, custom_formatters)
|
unmarshalled = subschema.unmarshal(value, custom_formatters)
|
||||||
except (OpenAPISchemaError, TypeError, ValueError):
|
except UnmarshallError:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
if result is not None:
|
if result is not None:
|
||||||
|
@ -285,9 +281,7 @@ class Schema(object):
|
||||||
unmarshal_callable = unmarshal_mapping[schema_type]
|
unmarshal_callable = unmarshal_mapping[schema_type]
|
||||||
try:
|
try:
|
||||||
return unmarshal_callable(value)
|
return unmarshal_callable(value)
|
||||||
except UnmarshallerStrictTypeError:
|
except (UnmarshallError, ValueError):
|
||||||
continue
|
|
||||||
except (OpenAPISchemaError, TypeError):
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
log.warning("failed to unmarshal any type")
|
log.warning("failed to unmarshal any type")
|
||||||
|
@ -295,7 +289,7 @@ class Schema(object):
|
||||||
|
|
||||||
def _unmarshal_collection(self, value, custom_formatters=None, strict=True):
|
def _unmarshal_collection(self, value, custom_formatters=None, strict=True):
|
||||||
if not isinstance(value, (list, tuple)):
|
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(
|
f = functools.partial(
|
||||||
self.items.unmarshal,
|
self.items.unmarshal,
|
||||||
|
@ -306,7 +300,7 @@ class Schema(object):
|
||||||
def _unmarshal_object(self, value, model_factory=None,
|
def _unmarshal_object(self, value, model_factory=None,
|
||||||
custom_formatters=None, strict=True):
|
custom_formatters=None, strict=True):
|
||||||
if not isinstance(value, (dict, )):
|
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()
|
model_factory = model_factory or ModelFactory()
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from openapi_core.schema.schemas.exceptions import (
|
||||||
OpenAPISchemaError,
|
OpenAPISchemaError,
|
||||||
InvalidSchemaProperty,
|
InvalidSchemaProperty,
|
||||||
UnmarshallerStrictTypeError,
|
UnmarshallerStrictTypeError,
|
||||||
|
FormatterNotFoundError,
|
||||||
)
|
)
|
||||||
from openapi_core.schema.schemas.util import (
|
from openapi_core.schema.schemas.util import (
|
||||||
forcebool, format_date, format_datetime, format_byte, format_uuid,
|
forcebool, format_date, format_datetime, format_byte, format_uuid,
|
||||||
|
@ -49,17 +50,12 @@ class PrimitiveTypeUnmarshaller(StrictUnmarshaller):
|
||||||
formatter = formatters.get(schema_format)
|
formatter = formatters.get(schema_format)
|
||||||
|
|
||||||
if formatter is None:
|
if formatter is None:
|
||||||
raise InvalidSchemaValue(
|
raise FormatterNotFoundError(value, type_format)
|
||||||
"Unsupported format {type} unmarshalling "
|
|
||||||
"for value {value}",
|
|
||||||
value, type_format)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return formatter(value)
|
return formatter(value)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise InvalidCustomFormatSchemaValue(
|
raise InvalidCustomFormatSchemaValue(value, type_format, exc)
|
||||||
"Failed to format value {value} to format {type}: {exception}",
|
|
||||||
value, type_format, exc)
|
|
||||||
|
|
||||||
def get_formatters(self):
|
def get_formatters(self):
|
||||||
return self.FORMATTERS
|
return self.FORMATTERS
|
||||||
|
|
|
@ -7,7 +7,9 @@ import pytest
|
||||||
from openapi_core.extensions.models.models import Model
|
from openapi_core.extensions.models.models import Model
|
||||||
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
|
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
|
||||||
from openapi_core.schema.schemas.exceptions import (
|
from openapi_core.schema.schemas.exceptions import (
|
||||||
InvalidSchemaValue, OpenAPISchemaError,
|
InvalidSchemaValue, OpenAPISchemaError, UnmarshallerStrictTypeError,
|
||||||
|
UnmarshallValueError, UnmarshallError, InvalidCustomFormatSchemaValue,
|
||||||
|
FormatterNotFoundError,
|
||||||
)
|
)
|
||||||
from openapi_core.schema.schemas.models import Schema
|
from openapi_core.schema.schemas.models import Schema
|
||||||
|
|
||||||
|
@ -88,14 +90,14 @@ class TestSchemaUnmarshal(object):
|
||||||
schema = Schema('string')
|
schema = Schema('string')
|
||||||
value = 1.23
|
value = 1.23
|
||||||
|
|
||||||
with pytest.raises(InvalidSchemaValue):
|
with pytest.raises(UnmarshallerStrictTypeError):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
def test_string_none(self):
|
def test_string_none(self):
|
||||||
schema = Schema('string')
|
schema = Schema('string')
|
||||||
value = None
|
value = None
|
||||||
|
|
||||||
with pytest.raises(InvalidSchemaValue):
|
with pytest.raises(UnmarshallError):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
def test_string_default(self):
|
def test_string_default(self):
|
||||||
|
@ -103,7 +105,7 @@ class TestSchemaUnmarshal(object):
|
||||||
schema = Schema('string', default=default_value)
|
schema = Schema('string', default=default_value)
|
||||||
value = None
|
value = None
|
||||||
|
|
||||||
with pytest.raises(InvalidSchemaValue):
|
with pytest.raises(UnmarshallError):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
def test_string_default_nullable(self):
|
def test_string_default_nullable(self):
|
||||||
|
@ -150,7 +152,7 @@ class TestSchemaUnmarshal(object):
|
||||||
schema = Schema('string', schema_format=custom_format)
|
schema = Schema('string', schema_format=custom_format)
|
||||||
value = 'x'
|
value = 'x'
|
||||||
|
|
||||||
with pytest.raises(InvalidSchemaValue):
|
with pytest.raises(InvalidCustomFormatSchemaValue):
|
||||||
schema.unmarshal(
|
schema.unmarshal(
|
||||||
value, custom_formatters={custom_format: custom_formatter})
|
value, custom_formatters={custom_format: custom_formatter})
|
||||||
|
|
||||||
|
@ -168,7 +170,10 @@ class TestSchemaUnmarshal(object):
|
||||||
value = 'x'
|
value = 'x'
|
||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
InvalidSchemaValue, message='Failed to format value'
|
FormatterNotFoundError,
|
||||||
|
message=(
|
||||||
|
'Formatter not found for custom format to unmarshal value x'
|
||||||
|
),
|
||||||
):
|
):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
|
@ -184,14 +189,14 @@ class TestSchemaUnmarshal(object):
|
||||||
schema = Schema('integer')
|
schema = Schema('integer')
|
||||||
value = '123'
|
value = '123'
|
||||||
|
|
||||||
with pytest.raises(InvalidSchemaValue):
|
with pytest.raises(UnmarshallerStrictTypeError):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
def test_integer_enum_invalid(self):
|
def test_integer_enum_invalid(self):
|
||||||
schema = Schema('integer', enum=[1, 2, 3])
|
schema = Schema('integer', enum=[1, 2, 3])
|
||||||
value = '123'
|
value = '123'
|
||||||
|
|
||||||
with pytest.raises(InvalidSchemaValue):
|
with pytest.raises(UnmarshallError):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
def test_integer_enum(self):
|
def test_integer_enum(self):
|
||||||
|
@ -206,7 +211,7 @@ class TestSchemaUnmarshal(object):
|
||||||
schema = Schema('integer', enum=[1, 2, 3])
|
schema = Schema('integer', enum=[1, 2, 3])
|
||||||
value = '2'
|
value = '2'
|
||||||
|
|
||||||
with pytest.raises(InvalidSchemaValue):
|
with pytest.raises(UnmarshallError):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
def test_integer_default(self):
|
def test_integer_default(self):
|
||||||
|
@ -214,7 +219,7 @@ class TestSchemaUnmarshal(object):
|
||||||
schema = Schema('integer', default=default_value)
|
schema = Schema('integer', default=default_value)
|
||||||
value = None
|
value = None
|
||||||
|
|
||||||
with pytest.raises(InvalidSchemaValue):
|
with pytest.raises(UnmarshallError):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
def test_integer_default_nullable(self):
|
def test_integer_default_nullable(self):
|
||||||
|
@ -230,7 +235,7 @@ class TestSchemaUnmarshal(object):
|
||||||
schema = Schema('integer')
|
schema = Schema('integer')
|
||||||
value = 'abc'
|
value = 'abc'
|
||||||
|
|
||||||
with pytest.raises(InvalidSchemaValue):
|
with pytest.raises(UnmarshallerStrictTypeError):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
def test_array_valid(self):
|
def test_array_valid(self):
|
||||||
|
@ -245,14 +250,14 @@ class TestSchemaUnmarshal(object):
|
||||||
schema = Schema('array', items=Schema('string'))
|
schema = Schema('array', items=Schema('string'))
|
||||||
value = '123'
|
value = '123'
|
||||||
|
|
||||||
with pytest.raises(InvalidSchemaValue):
|
with pytest.raises(UnmarshallValueError):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
def test_array_of_integer_string_invalid(self):
|
def test_array_of_integer_string_invalid(self):
|
||||||
schema = Schema('array', items=Schema('integer'))
|
schema = Schema('array', items=Schema('integer'))
|
||||||
value = '123'
|
value = '123'
|
||||||
|
|
||||||
with pytest.raises(InvalidSchemaValue):
|
with pytest.raises(UnmarshallValueError):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
def test_boolean_valid(self):
|
def test_boolean_valid(self):
|
||||||
|
@ -267,7 +272,7 @@ class TestSchemaUnmarshal(object):
|
||||||
schema = Schema('boolean')
|
schema = Schema('boolean')
|
||||||
value = 'True'
|
value = 'True'
|
||||||
|
|
||||||
with pytest.raises(InvalidSchemaValue):
|
with pytest.raises(UnmarshallerStrictTypeError):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
def test_number_valid(self):
|
def test_number_valid(self):
|
||||||
|
@ -282,7 +287,7 @@ class TestSchemaUnmarshal(object):
|
||||||
schema = Schema('number')
|
schema = Schema('number')
|
||||||
value = '1.23'
|
value = '1.23'
|
||||||
|
|
||||||
with pytest.raises(InvalidSchemaValue):
|
with pytest.raises(UnmarshallerStrictTypeError):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
def test_number_int(self):
|
def test_number_int(self):
|
||||||
|
|
Loading…
Reference in a new issue