Skip to content

Schema exceptions refactor #168

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions openapi_core/schema/media_types/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
from json import loads

from openapi_core.schema.media_types.exceptions import InvalidMediaTypeValue
from openapi_core.schema.schemas.exceptions import OpenAPISchemaError
from openapi_core.schema.schemas.exceptions import (
CastError, ValidateError, UnmarshalError,
)


MEDIA_TYPE_DESERIALIZERS = {
Expand Down Expand Up @@ -37,20 +39,25 @@ def cast(self, value):
return value

try:
return self.deserialize(value)
deserialized = self.deserialize(value)
except ValueError as exc:
raise InvalidMediaTypeValue(exc)

try:
return self.schema.cast(deserialized)
except CastError as exc:
raise InvalidMediaTypeValue(exc)

def unmarshal(self, value, custom_formatters=None, resolver=None):
if not self.schema:
return value

try:
self.schema.validate(value, resolver=resolver)
except OpenAPISchemaError as exc:
except ValidateError as exc:
raise InvalidMediaTypeValue(exc)

try:
return self.schema.unmarshal(value, custom_formatters=custom_formatters)
except OpenAPISchemaError as exc:
except UnmarshalError as exc:
raise InvalidMediaTypeValue(exc)
9 changes: 7 additions & 2 deletions openapi_core/schema/parameters/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,21 @@ class OpenAPIParameterError(OpenAPIMappingError):
pass


class MissingParameterError(OpenAPIParameterError):
"""Missing parameter error"""
pass


@attr.s(hash=True)
class MissingParameter(OpenAPIParameterError):
class MissingParameter(MissingParameterError):
name = attr.ib()

def __str__(self):
return "Missing parameter (without default value): {0}".format(self.name)


@attr.s(hash=True)
class MissingRequiredParameter(OpenAPIParameterError):
class MissingRequiredParameter(MissingParameterError):
name = attr.ib()

def __str__(self):
Expand Down
10 changes: 6 additions & 4 deletions openapi_core/schema/parameters/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
EmptyParameterValue,
)
from openapi_core.schema.schemas.enums import SchemaType
from openapi_core.schema.schemas.exceptions import OpenAPISchemaError
from openapi_core.schema.schemas.exceptions import (
CastError, ValidateError, UnmarshalError,
)

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -110,7 +112,7 @@ def cast(self, value):

try:
return self.schema.cast(deserialized)
except OpenAPISchemaError as exc:
except CastError as exc:
raise InvalidParameterValue(self.name, exc)

def unmarshal(self, value, custom_formatters=None, resolver=None):
Expand All @@ -119,7 +121,7 @@ def unmarshal(self, value, custom_formatters=None, resolver=None):

try:
self.schema.validate(value, resolver=resolver)
except OpenAPISchemaError as exc:
except ValidateError as exc:
raise InvalidParameterValue(self.name, exc)

try:
Expand All @@ -128,5 +130,5 @@ def unmarshal(self, value, custom_formatters=None, resolver=None):
custom_formatters=custom_formatters,
strict=True,
)
except OpenAPISchemaError as exc:
except UnmarshalError as exc:
raise InvalidParameterValue(self.name, exc)
85 changes: 54 additions & 31 deletions openapi_core/schema/schemas/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,72 +8,95 @@ class OpenAPISchemaError(OpenAPIMappingError):


@attr.s(hash=True)
class NoValidSchema(OpenAPISchemaError):
class CastError(OpenAPISchemaError):
"""Schema cast operation error"""
value = attr.ib()
type = attr.ib()

def __str__(self):
return "No valid schema found for value: {0}".format(self.value)
return "Failed to cast value {value} to type {type}".format(
value=self.value, type=self.type)


@attr.s(hash=True)
class UndefinedItemsSchema(OpenAPISchemaError):
type = attr.ib()
class ValidateError(OpenAPISchemaError):
"""Schema validate operation error"""
pass

def __str__(self):
return "Null value for schema type {0}".format(self.type)

class UnmarshalError(OpenAPISchemaError):
"""Schema unmarshal operation error"""
pass


@attr.s(hash=True)
class InvalidSchemaValue(OpenAPISchemaError):
msg = attr.ib()
class UnmarshalValueError(UnmarshalError):
"""Failed to unmarshal value to type"""
value = attr.ib()
type = attr.ib()
original_exception = attr.ib(default=None)

def __str__(self):
return self.msg.format(value=self.value, type=self.type)
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 InvalidCustomFormatSchemaValue(InvalidSchemaValue):
original_exception = attr.ib()
class InvalidSchemaValue(ValidateError):
value = attr.ib()
type = attr.ib()
schema_errors = attr.ib()

def __str__(self):
return self.msg.format(value=self.value, type=self.type, exception=self.original_exception)
errors = list(self.schema_errors)
return (
"Value {value} not valid for schema of type {type}: {errors}"
).format(value=self.value, type=self.type, errors=errors)


@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)
class UnmarshallerError(UnmarshalError):
"""Unmarshaller error"""
pass


@attr.s(hash=True)
class InvalidSchemaProperty(OpenAPISchemaError):
property_name = attr.ib()
class InvalidCustomFormatSchemaValue(UnmarshallerError):
"""Value failed to format with custom formatter"""
value = attr.ib()
type = attr.ib()
original_exception = attr.ib()

def __str__(self):
return "Invalid schema property {0}: {1}".format(self.property_name, self.original_exception)
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 MissingSchemaProperty(OpenAPISchemaError):
property_name = attr.ib()
class FormatterNotFoundError(UnmarshallerError):
"""Formatter not found to unmarshal"""
value = attr.ib()
type_format = attr.ib()

def __str__(self):
return "Missing schema property: {0}".format(self.property_name)


class UnmarshallerError(OpenAPIMappingError):
pass
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)
52 changes: 15 additions & 37 deletions openapi_core/schema/schemas/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@
from openapi_core.schema.schemas._format import oas30_format_checker
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,
CastError, InvalidSchemaValue,
UnmarshallerError, UnmarshalValueError, UnmarshalError,
)
from openapi_core.schema.schemas.util import (
forcebool, format_date, format_datetime, format_byte, format_uuid,
Expand Down Expand Up @@ -141,13 +139,6 @@ def get_all_required_properties_names(self):

return set(required)

def are_additional_properties_allowed(self, one_of_schema=None):
return (
(self.additional_properties is not False) and
(one_of_schema is None or
one_of_schema.additional_properties is not False)
)

def get_cast_mapping(self):
mapping = self.TYPE_CAST_CALLABLE_GETTER.copy()
mapping.update({
Expand All @@ -167,8 +158,7 @@ def cast(self, value):
try:
return cast_callable(value)
except ValueError:
raise InvalidSchemaValue(
"Failed to cast value {value} to type {type}", value, self.type)
raise CastError(value, self.type)

def _cast_collection(self, value):
return list(map(self.items.cast, value))
Expand Down Expand Up @@ -203,21 +193,21 @@ def validate(self, value, resolver=None):
try:
return validator.validate(value)
except ValidationError:
# TODO: pass validation errors
raise InvalidSchemaValue("Value not valid for schema", value, self.type)
errors_iter = validator.iter_errors(value)
raise InvalidSchemaValue(value, self.type, errors_iter)

def unmarshal(self, value, custom_formatters=None, strict=True):
"""Unmarshal parameter from the value."""
if self.deprecated:
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 UnmarshalError(
"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 UnmarshalError("Invalid value for enum: {0}".format(value))

unmarshal_mapping = self.get_unmarshal_mapping(
custom_formatters=custom_formatters, strict=strict)
Expand All @@ -228,12 +218,8 @@ def unmarshal(self, value, custom_formatters=None, strict=True):
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 UnmarshalValueError(value, self.type, exc)

return unmarshalled

Expand Down Expand Up @@ -268,7 +254,7 @@ def _unmarshal_any(self, value, custom_formatters=None, strict=True):
for subschema in self.one_of:
try:
unmarshalled = subschema.unmarshal(value, custom_formatters)
except (OpenAPISchemaError, TypeError, ValueError):
except UnmarshalError:
continue
else:
if result is not None:
Expand All @@ -285,17 +271,15 @@ def _unmarshal_any(self, value, custom_formatters=None, strict=True):
unmarshal_callable = unmarshal_mapping[schema_type]
try:
return unmarshal_callable(value)
except UnmarshallerStrictTypeError:
continue
except (OpenAPISchemaError, TypeError):
except (UnmarshalError, ValueError):
continue

log.warning("failed to unmarshal any type")
return value

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,
Expand All @@ -306,7 +290,7 @@ def _unmarshal_collection(self, value, custom_formatters=None, strict=True):
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()

Expand All @@ -316,7 +300,7 @@ def _unmarshal_object(self, value, model_factory=None,
try:
unmarshalled = self._unmarshal_properties(
value, one_of_schema, custom_formatters=custom_formatters)
except OpenAPISchemaError:
except (UnmarshalError, ValueError):
pass
else:
if properties is not None:
Expand Down Expand Up @@ -348,10 +332,6 @@ def _unmarshal_properties(self, value, one_of_schema=None,

value_props_names = value.keys()
extra_props = set(value_props_names) - set(all_props_names)
extra_props_allowed = self.are_additional_properties_allowed(
one_of_schema)
if extra_props and not extra_props_allowed:
raise UndefinedSchemaProperty(extra_props)

properties = {}
if self.additional_properties is not True:
Expand All @@ -364,8 +344,6 @@ def _unmarshal_properties(self, value, one_of_schema=None,
try:
prop_value = value[prop_name]
except KeyError:
if prop_name in all_req_props_names:
raise MissingSchemaProperty(prop_name)
if not prop.nullable and not prop.default:
continue
prop_value = prop.default
Expand Down
Loading