Skip to content

Commit 4c4636a

Browse files
authored
Merge pull request #168 from p1c2u/feature/schema-exceptions-refactor
Schema exceptions refactor
2 parents fd99117 + cfdf341 commit 4c4636a

File tree

10 files changed

+159
-125
lines changed

10 files changed

+159
-125
lines changed

openapi_core/schema/media_types/models.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
from json import loads
55

66
from openapi_core.schema.media_types.exceptions import InvalidMediaTypeValue
7-
from openapi_core.schema.schemas.exceptions import OpenAPISchemaError
7+
from openapi_core.schema.schemas.exceptions import (
8+
CastError, ValidateError, UnmarshalError,
9+
)
810

911

1012
MEDIA_TYPE_DESERIALIZERS = {
@@ -37,20 +39,25 @@ def cast(self, value):
3739
return value
3840

3941
try:
40-
return self.deserialize(value)
42+
deserialized = self.deserialize(value)
4143
except ValueError as exc:
4244
raise InvalidMediaTypeValue(exc)
4345

46+
try:
47+
return self.schema.cast(deserialized)
48+
except CastError as exc:
49+
raise InvalidMediaTypeValue(exc)
50+
4451
def unmarshal(self, value, custom_formatters=None, resolver=None):
4552
if not self.schema:
4653
return value
4754

4855
try:
4956
self.schema.validate(value, resolver=resolver)
50-
except OpenAPISchemaError as exc:
57+
except ValidateError as exc:
5158
raise InvalidMediaTypeValue(exc)
5259

5360
try:
5461
return self.schema.unmarshal(value, custom_formatters=custom_formatters)
55-
except OpenAPISchemaError as exc:
62+
except UnmarshalError as exc:
5663
raise InvalidMediaTypeValue(exc)

openapi_core/schema/parameters/exceptions.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,21 @@ class OpenAPIParameterError(OpenAPIMappingError):
77
pass
88

99

10+
class MissingParameterError(OpenAPIParameterError):
11+
"""Missing parameter error"""
12+
pass
13+
14+
1015
@attr.s(hash=True)
11-
class MissingParameter(OpenAPIParameterError):
16+
class MissingParameter(MissingParameterError):
1217
name = attr.ib()
1318

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

1722

1823
@attr.s(hash=True)
19-
class MissingRequiredParameter(OpenAPIParameterError):
24+
class MissingRequiredParameter(MissingParameterError):
2025
name = attr.ib()
2126

2227
def __str__(self):

openapi_core/schema/parameters/models.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
EmptyParameterValue,
1111
)
1212
from openapi_core.schema.schemas.enums import SchemaType
13-
from openapi_core.schema.schemas.exceptions import OpenAPISchemaError
13+
from openapi_core.schema.schemas.exceptions import (
14+
CastError, ValidateError, UnmarshalError,
15+
)
1416

1517
log = logging.getLogger(__name__)
1618

@@ -110,7 +112,7 @@ def cast(self, value):
110112

111113
try:
112114
return self.schema.cast(deserialized)
113-
except OpenAPISchemaError as exc:
115+
except CastError as exc:
114116
raise InvalidParameterValue(self.name, exc)
115117

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

120122
try:
121123
self.schema.validate(value, resolver=resolver)
122-
except OpenAPISchemaError as exc:
124+
except ValidateError as exc:
123125
raise InvalidParameterValue(self.name, exc)
124126

125127
try:
@@ -128,5 +130,5 @@ def unmarshal(self, value, custom_formatters=None, resolver=None):
128130
custom_formatters=custom_formatters,
129131
strict=True,
130132
)
131-
except OpenAPISchemaError as exc:
133+
except UnmarshalError as exc:
132134
raise InvalidParameterValue(self.name, exc)

openapi_core/schema/schemas/exceptions.py

+54-31
Original file line numberDiff line numberDiff line change
@@ -8,72 +8,95 @@ class OpenAPISchemaError(OpenAPIMappingError):
88

99

1010
@attr.s(hash=True)
11-
class NoValidSchema(OpenAPISchemaError):
11+
class CastError(OpenAPISchemaError):
12+
"""Schema cast operation error"""
1213
value = attr.ib()
14+
type = attr.ib()
1315

1416
def __str__(self):
15-
return "No valid schema found for value: {0}".format(self.value)
17+
return "Failed to cast value {value} to type {type}".format(
18+
value=self.value, type=self.type)
1619

1720

18-
@attr.s(hash=True)
19-
class UndefinedItemsSchema(OpenAPISchemaError):
20-
type = attr.ib()
21+
class ValidateError(OpenAPISchemaError):
22+
"""Schema validate operation error"""
23+
pass
2124

22-
def __str__(self):
23-
return "Null value for schema type {0}".format(self.type)
25+
26+
class UnmarshalError(OpenAPISchemaError):
27+
"""Schema unmarshal operation error"""
28+
pass
2429

2530

2631
@attr.s(hash=True)
27-
class InvalidSchemaValue(OpenAPISchemaError):
28-
msg = attr.ib()
32+
class UnmarshalValueError(UnmarshalError):
33+
"""Failed to unmarshal value to type"""
2934
value = attr.ib()
3035
type = attr.ib()
36+
original_exception = attr.ib(default=None)
3137

3238
def __str__(self):
33-
return self.msg.format(value=self.value, type=self.type)
39+
return (
40+
"Failed to unmarshal value {value} to type {type}: {exception}"
41+
).format(
42+
value=self.value, type=self.type,
43+
exception=self.original_exception,
44+
)
3445

3546

3647
@attr.s(hash=True)
37-
class InvalidCustomFormatSchemaValue(InvalidSchemaValue):
38-
original_exception = attr.ib()
48+
class InvalidSchemaValue(ValidateError):
49+
value = attr.ib()
50+
type = attr.ib()
51+
schema_errors = attr.ib()
3952

4053
def __str__(self):
41-
return self.msg.format(value=self.value, type=self.type, exception=self.original_exception)
54+
errors = list(self.schema_errors)
55+
return (
56+
"Value {value} not valid for schema of type {type}: {errors}"
57+
).format(value=self.value, type=self.type, errors=errors)
4258

4359

44-
@attr.s(hash=True)
45-
class UndefinedSchemaProperty(OpenAPISchemaError):
46-
extra_props = attr.ib()
47-
48-
def __str__(self):
49-
return "Extra unexpected properties found in schema: {0}".format(self.extra_props)
60+
class UnmarshallerError(UnmarshalError):
61+
"""Unmarshaller error"""
62+
pass
5063

5164

5265
@attr.s(hash=True)
53-
class InvalidSchemaProperty(OpenAPISchemaError):
54-
property_name = attr.ib()
66+
class InvalidCustomFormatSchemaValue(UnmarshallerError):
67+
"""Value failed to format with custom formatter"""
68+
value = attr.ib()
69+
type = attr.ib()
5570
original_exception = attr.ib()
5671

5772
def __str__(self):
58-
return "Invalid schema property {0}: {1}".format(self.property_name, self.original_exception)
73+
return (
74+
"Failed to format value {value} to format {type}: {exception}"
75+
).format(
76+
value=self.value, type=self.type,
77+
exception=self.original_exception,
78+
)
5979

6080

6181
@attr.s(hash=True)
62-
class MissingSchemaProperty(OpenAPISchemaError):
63-
property_name = attr.ib()
82+
class FormatterNotFoundError(UnmarshallerError):
83+
"""Formatter not found to unmarshal"""
84+
value = attr.ib()
85+
type_format = attr.ib()
6486

6587
def __str__(self):
66-
return "Missing schema property: {0}".format(self.property_name)
67-
68-
69-
class UnmarshallerError(OpenAPIMappingError):
70-
pass
88+
return (
89+
"Formatter not found for {format} format "
90+
"to unmarshal value {value}"
91+
).format(format=self.type_format, value=self.value)
7192

7293

94+
@attr.s(hash=True)
7395
class UnmarshallerStrictTypeError(UnmarshallerError):
7496
value = attr.ib()
7597
types = attr.ib()
7698

7799
def __str__(self):
78-
return "Value {value} is not one of types {types}".format(
79-
self.value, self.types)
100+
types = ', '.join(list(map(str, self.types)))
101+
return "Value {value} is not one of types: {types}".format(
102+
value=self.value, types=types)

openapi_core/schema/schemas/models.py

+15-37
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@
1515
from openapi_core.schema.schemas._format import oas30_format_checker
1616
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
1717
from openapi_core.schema.schemas.exceptions import (
18-
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
19-
OpenAPISchemaError, NoValidSchema,
20-
UndefinedItemsSchema, InvalidCustomFormatSchemaValue, InvalidSchemaProperty,
21-
UnmarshallerStrictTypeError,
18+
CastError, InvalidSchemaValue,
19+
UnmarshallerError, UnmarshalValueError, UnmarshalError,
2220
)
2321
from openapi_core.schema.schemas.util import (
2422
forcebool, format_date, format_datetime, format_byte, format_uuid,
@@ -141,13 +139,6 @@ def get_all_required_properties_names(self):
141139

142140
return set(required)
143141

144-
def are_additional_properties_allowed(self, one_of_schema=None):
145-
return (
146-
(self.additional_properties is not False) and
147-
(one_of_schema is None or
148-
one_of_schema.additional_properties is not False)
149-
)
150-
151142
def get_cast_mapping(self):
152143
mapping = self.TYPE_CAST_CALLABLE_GETTER.copy()
153144
mapping.update({
@@ -167,8 +158,7 @@ def cast(self, value):
167158
try:
168159
return cast_callable(value)
169160
except ValueError:
170-
raise InvalidSchemaValue(
171-
"Failed to cast value {value} to type {type}", value, self.type)
161+
raise CastError(value, self.type)
172162

173163
def _cast_collection(self, value):
174164
return list(map(self.items.cast, value))
@@ -203,21 +193,21 @@ def validate(self, value, resolver=None):
203193
try:
204194
return validator.validate(value)
205195
except ValidationError:
206-
# TODO: pass validation errors
207-
raise InvalidSchemaValue("Value not valid for schema", value, self.type)
196+
errors_iter = validator.iter_errors(value)
197+
raise InvalidSchemaValue(value, self.type, errors_iter)
208198

209199
def unmarshal(self, value, custom_formatters=None, strict=True):
210200
"""Unmarshal parameter from the value."""
211201
if self.deprecated:
212202
warnings.warn("The schema is deprecated", DeprecationWarning)
213203
if value is None:
214204
if not self.nullable:
215-
raise InvalidSchemaValue("Null value for non-nullable schema", value, self.type)
205+
raise UnmarshalError(
206+
"Null value for non-nullable schema", value, self.type)
216207
return self.default
217208

218209
if self.enum and value not in self.enum:
219-
raise InvalidSchemaValue(
220-
"Value {value} not in enum choices: {type}", value, self.enum)
210+
raise UnmarshalError("Invalid value for enum: {0}".format(value))
221211

222212
unmarshal_mapping = self.get_unmarshal_mapping(
223213
custom_formatters=custom_formatters, strict=strict)
@@ -228,12 +218,8 @@ def unmarshal(self, value, custom_formatters=None, strict=True):
228218
unmarshal_callable = unmarshal_mapping[self.type]
229219
try:
230220
unmarshalled = unmarshal_callable(value)
231-
except UnmarshallerStrictTypeError:
232-
raise InvalidSchemaValue(
233-
"Value {value} is not of type {type}", value, self.type)
234-
except ValueError:
235-
raise InvalidSchemaValue(
236-
"Failed to unmarshal value {value} to type {type}", value, self.type)
221+
except ValueError as exc:
222+
raise UnmarshalValueError(value, self.type, exc)
237223

238224
return unmarshalled
239225

@@ -268,7 +254,7 @@ def _unmarshal_any(self, value, custom_formatters=None, strict=True):
268254
for subschema in self.one_of:
269255
try:
270256
unmarshalled = subschema.unmarshal(value, custom_formatters)
271-
except (OpenAPISchemaError, TypeError, ValueError):
257+
except UnmarshalError:
272258
continue
273259
else:
274260
if result is not None:
@@ -285,17 +271,15 @@ def _unmarshal_any(self, value, custom_formatters=None, strict=True):
285271
unmarshal_callable = unmarshal_mapping[schema_type]
286272
try:
287273
return unmarshal_callable(value)
288-
except UnmarshallerStrictTypeError:
289-
continue
290-
except (OpenAPISchemaError, TypeError):
274+
except (UnmarshalError, ValueError):
291275
continue
292276

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

296280
def _unmarshal_collection(self, value, custom_formatters=None, strict=True):
297281
if not isinstance(value, (list, tuple)):
298-
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
282+
raise ValueError("Invalid value for collection: {0}".format(value))
299283

300284
f = functools.partial(
301285
self.items.unmarshal,
@@ -306,7 +290,7 @@ def _unmarshal_collection(self, value, custom_formatters=None, strict=True):
306290
def _unmarshal_object(self, value, model_factory=None,
307291
custom_formatters=None, strict=True):
308292
if not isinstance(value, (dict, )):
309-
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
293+
raise ValueError("Invalid value for object: {0}".format(value))
310294

311295
model_factory = model_factory or ModelFactory()
312296

@@ -316,7 +300,7 @@ def _unmarshal_object(self, value, model_factory=None,
316300
try:
317301
unmarshalled = self._unmarshal_properties(
318302
value, one_of_schema, custom_formatters=custom_formatters)
319-
except OpenAPISchemaError:
303+
except (UnmarshalError, ValueError):
320304
pass
321305
else:
322306
if properties is not None:
@@ -348,10 +332,6 @@ def _unmarshal_properties(self, value, one_of_schema=None,
348332

349333
value_props_names = value.keys()
350334
extra_props = set(value_props_names) - set(all_props_names)
351-
extra_props_allowed = self.are_additional_properties_allowed(
352-
one_of_schema)
353-
if extra_props and not extra_props_allowed:
354-
raise UndefinedSchemaProperty(extra_props)
355335

356336
properties = {}
357337
if self.additional_properties is not True:
@@ -364,8 +344,6 @@ def _unmarshal_properties(self, value, one_of_schema=None,
364344
try:
365345
prop_value = value[prop_name]
366346
except KeyError:
367-
if prop_name in all_req_props_names:
368-
raise MissingSchemaProperty(prop_name)
369347
if not prop.nullable and not prop.default:
370348
continue
371349
prop_value = prop.default

0 commit comments

Comments
 (0)