mirror of
https://github.com/correl/openapi-core.git
synced 2024-11-22 03:00:10 +00:00
Rewrok exception handling
Main motivation behind this change is to be able to catch exceptions as per raise_for_errors() helpers, but to inspect state of exceptions instead of just getting a rendered string. This allows rendering exceptions into JSON, for example.
This commit is contained in:
parent
89a53f6edc
commit
6bdd1a6756
16 changed files with 167 additions and 93 deletions
|
@ -1,9 +1,16 @@
|
|||
from openapi_core.schema.exceptions import OpenAPIMappingError
|
||||
|
||||
import attr
|
||||
|
||||
|
||||
class OpenAPIContentError(OpenAPIMappingError):
|
||||
pass
|
||||
|
||||
|
||||
@attr.s
|
||||
class MimeTypeNotFound(OpenAPIContentError):
|
||||
pass
|
||||
mimetype = attr.ib()
|
||||
availableMimetypes = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "Mimetype not found: {0}. Valid mimetypes: {1}".format(self.mimetype, self.availableMimetypes)
|
||||
|
|
|
@ -18,4 +18,4 @@ class Content(dict):
|
|||
if fnmatch.fnmatch(mimetype, key):
|
||||
return value
|
||||
|
||||
raise MimeTypeNotFound("{0} mimetype not found")
|
||||
raise MimeTypeNotFound(mimetype, self.keys())
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
from openapi_core.schema.exceptions import OpenAPIMappingError
|
||||
|
||||
import attr
|
||||
|
||||
|
||||
class OpenAPIMediaTypeError(OpenAPIMappingError):
|
||||
pass
|
||||
|
||||
|
||||
@attr.s
|
||||
class InvalidMediaTypeValue(OpenAPIMediaTypeError):
|
||||
pass
|
||||
original_exception = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "Mimetype invalid: {0}".format(self.original_exception)
|
||||
|
||||
@attr.s
|
||||
class InvalidContentType(OpenAPIMediaTypeError):
|
||||
pass
|
||||
mimetype = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "Content for following mimetype not found: {0}".format(self.mimetype)
|
||||
|
|
|
@ -39,14 +39,14 @@ class MediaType(object):
|
|||
try:
|
||||
deserialized = self.deserialize(value)
|
||||
except ValueError as exc:
|
||||
raise InvalidMediaTypeValue(str(exc))
|
||||
raise InvalidMediaTypeValue(exc)
|
||||
|
||||
try:
|
||||
unmarshalled = self.schema.unmarshal(deserialized, custom_formatters=custom_formatters)
|
||||
except InvalidSchemaValue as exc:
|
||||
raise InvalidMediaTypeValue(str(exc))
|
||||
raise InvalidMediaTypeValue(exc)
|
||||
|
||||
try:
|
||||
return self.schema.validate(unmarshalled, custom_formatters=custom_formatters)
|
||||
except InvalidSchemaValue as exc:
|
||||
raise InvalidMediaTypeValue(str(exc))
|
||||
raise InvalidMediaTypeValue(exc)
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
from openapi_core.schema.exceptions import OpenAPIMappingError
|
||||
|
||||
import attr
|
||||
|
||||
|
||||
class OpenAPIOperationError(OpenAPIMappingError):
|
||||
pass
|
||||
|
||||
|
||||
@attr.s
|
||||
class InvalidOperation(OpenAPIOperationError):
|
||||
pass
|
||||
path_pattern = attr.ib()
|
||||
http_method = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "Unknown operation path {0} with method {1}".format(
|
||||
self.path_pattern, self.http_method)
|
||||
|
|
|
@ -32,7 +32,6 @@ class Operation(object):
|
|||
return self.responses[http_status_range]
|
||||
|
||||
if 'default' not in self.responses:
|
||||
raise InvalidResponse(
|
||||
"Unknown response http status {0}".format(http_status))
|
||||
raise InvalidResponse(http_status, self.responses)
|
||||
|
||||
return self.responses['default']
|
||||
|
|
|
@ -1,21 +1,40 @@
|
|||
from openapi_core.schema.exceptions import OpenAPIMappingError
|
||||
|
||||
import attr
|
||||
|
||||
|
||||
class OpenAPIParameterError(OpenAPIMappingError):
|
||||
pass
|
||||
|
||||
|
||||
@attr.s
|
||||
class MissingParameter(OpenAPIParameterError):
|
||||
pass
|
||||
name = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "Missing parameter (without default value): {0}".format(self.name)
|
||||
|
||||
|
||||
@attr.s
|
||||
class MissingRequiredParameter(OpenAPIParameterError):
|
||||
pass
|
||||
name = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "Missing required parameter: {0}".format(self.name)
|
||||
|
||||
|
||||
@attr.s
|
||||
class EmptyParameterValue(OpenAPIParameterError):
|
||||
pass
|
||||
name = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "Value of parameter cannot be empty: {0}".format(self.name)
|
||||
|
||||
|
||||
@attr.s
|
||||
class InvalidParameterValue(OpenAPIParameterError):
|
||||
pass
|
||||
name = attr.ib()
|
||||
original_exception = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "Invalid parameter value for `{0}`: {1}".format(sef.name, self.original_exception)
|
||||
|
|
|
@ -77,12 +77,10 @@ class Parameter(object):
|
|||
|
||||
if self.name not in location:
|
||||
if self.required:
|
||||
raise MissingRequiredParameter(
|
||||
"Missing required `{0}` parameter".format(self.name))
|
||||
raise MissingRequiredParameter(self.name)
|
||||
|
||||
if not self.schema or self.schema.default is None:
|
||||
raise MissingParameter(
|
||||
"Missing `{0}` parameter".format(self.name))
|
||||
raise MissingParameter(self.name)
|
||||
|
||||
return self.schema.default
|
||||
|
||||
|
@ -100,8 +98,7 @@ class Parameter(object):
|
|||
|
||||
if (self.location == ParameterLocation.QUERY and value == "" and
|
||||
not self.allow_empty_value):
|
||||
raise EmptyParameterValue(
|
||||
"Value of {0} parameter cannot be empty".format(self.name))
|
||||
raise EmptyParameterValue(self.name)
|
||||
|
||||
if not self.schema:
|
||||
return value
|
||||
|
@ -109,14 +106,14 @@ class Parameter(object):
|
|||
try:
|
||||
deserialized = self.deserialize(value)
|
||||
except (ValueError, AttributeError) as exc:
|
||||
raise InvalidParameterValue(str(exc))
|
||||
raise InvalidParameterValue(self.name, exc)
|
||||
|
||||
try:
|
||||
unmarshalled = self.schema.unmarshal(deserialized, custom_formatters=custom_formatters)
|
||||
except InvalidSchemaValue as exc:
|
||||
raise InvalidParameterValue(str(exc))
|
||||
raise InvalidParameterValue(self.name, exc)
|
||||
|
||||
try:
|
||||
return self.schema.validate(unmarshalled, custom_formatters=custom_formatters)
|
||||
except InvalidSchemaValue as exc:
|
||||
raise InvalidParameterValue(str(exc))
|
||||
raise InvalidParameterValue(self.name, exc)
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
from openapi_core.schema.exceptions import OpenAPIMappingError
|
||||
|
||||
import attr
|
||||
|
||||
|
||||
class OpenAPIRequestBodyError(OpenAPIMappingError):
|
||||
pass
|
||||
|
||||
|
||||
@attr.s
|
||||
class MissingRequestBody(OpenAPIRequestBodyError):
|
||||
pass
|
||||
request = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "Missing required request body"
|
||||
|
|
|
@ -16,11 +16,9 @@ class RequestBody(object):
|
|||
try:
|
||||
return self.content[mimetype]
|
||||
except MimeTypeNotFound:
|
||||
raise InvalidContentType(
|
||||
"Invalid mime type `{0}`".format(mimetype))
|
||||
raise InvalidContentType(mimetype)
|
||||
|
||||
def get_value(self, request):
|
||||
if not request.body and self.required:
|
||||
raise MissingRequestBody("Missing required request body")
|
||||
|
||||
raise MissingRequestBody(request)
|
||||
return request.body
|
||||
|
|
|
@ -1,13 +1,24 @@
|
|||
from openapi_core.schema.exceptions import OpenAPIMappingError
|
||||
|
||||
import attr
|
||||
|
||||
|
||||
class OpenAPIResponseError(OpenAPIMappingError):
|
||||
pass
|
||||
|
||||
|
||||
@attr.s
|
||||
class InvalidResponse(OpenAPIResponseError):
|
||||
pass
|
||||
http_status = attr.ib()
|
||||
responses = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "Unknown response http status: {0}".format(str(self.http_status))
|
||||
|
||||
|
||||
@attr.s
|
||||
class MissingResponseContent(OpenAPIResponseError):
|
||||
pass
|
||||
response = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "Missing response content"
|
||||
|
|
|
@ -23,11 +23,10 @@ class Response(object):
|
|||
try:
|
||||
return self.content[mimetype]
|
||||
except MimeTypeNotFound:
|
||||
raise InvalidContentType(
|
||||
"Invalid mime type `{0}`".format(mimetype))
|
||||
raise InvalidContentType(mimetype)
|
||||
|
||||
def get_value(self, response):
|
||||
if not response.data:
|
||||
raise MissingResponseContent("Missing response content")
|
||||
raise MissingResponseContent(response)
|
||||
|
||||
return response.data
|
||||
|
|
|
@ -1,33 +1,72 @@
|
|||
from openapi_core.schema.exceptions import OpenAPIMappingError
|
||||
|
||||
import attr
|
||||
|
||||
|
||||
class OpenAPISchemaError(OpenAPIMappingError):
|
||||
pass
|
||||
|
||||
|
||||
@attr.s
|
||||
class NoValidSchema(OpenAPISchemaError):
|
||||
pass
|
||||
value = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "No valid schema found for value: {0}".format(self.value)
|
||||
|
||||
|
||||
@attr.s
|
||||
class UndefinedItemsSchema(OpenAPISchemaError):
|
||||
pass
|
||||
type = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "Null value for schema type {0}".format(self.type)
|
||||
|
||||
|
||||
@attr.s
|
||||
class InvalidSchemaValue(OpenAPISchemaError):
|
||||
pass
|
||||
msg = attr.ib()
|
||||
value = attr.ib()
|
||||
type = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return self.msg.format(value=self.value, type=self.type)
|
||||
|
||||
@attr.s
|
||||
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
|
||||
class UndefinedSchemaProperty(OpenAPISchemaError):
|
||||
pass
|
||||
extra_props = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "Extra unexpected properties found in schema: {0}".format(self.extra_props)
|
||||
|
||||
|
||||
@attr.s
|
||||
class MissingSchemaProperty(OpenAPISchemaError):
|
||||
pass
|
||||
property_name = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "Missing schema property: {0}".format(self.property_name)
|
||||
|
||||
|
||||
@attr.s
|
||||
class NoOneOfSchema(OpenAPISchemaError):
|
||||
pass
|
||||
type = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "Exactly one valid schema type {0} should be valid, None found.".format(self.type)
|
||||
|
||||
|
||||
@attr.s
|
||||
class MultipleOneOfSchema(OpenAPISchemaError):
|
||||
pass
|
||||
type = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "Exactly one schema type {0} should be valid, more than one found".format(self.type)
|
||||
|
|
|
@ -14,7 +14,7 @@ from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
|
|||
from openapi_core.schema.schemas.exceptions import (
|
||||
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
|
||||
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema, NoValidSchema,
|
||||
UndefinedItemsSchema,
|
||||
UndefinedItemsSchema, InvalidCustomFormatSchemaValue,
|
||||
)
|
||||
from openapi_core.schema.schemas.util import (
|
||||
forcebool, format_date, format_datetime,
|
||||
|
@ -160,7 +160,7 @@ class Schema(object):
|
|||
"""Cast value to schema type"""
|
||||
if value is None:
|
||||
if not self.nullable:
|
||||
raise InvalidSchemaValue("Null value for non-nullable schema")
|
||||
raise InvalidSchemaValue("Null value for non-nullable schema", value, self.type)
|
||||
return self.default
|
||||
|
||||
cast_mapping = self.get_cast_mapping(custom_formatters=custom_formatters)
|
||||
|
@ -173,8 +173,7 @@ class Schema(object):
|
|||
return cast_callable(value)
|
||||
except ValueError:
|
||||
raise InvalidSchemaValue(
|
||||
"Failed to cast value of {0} to {1}".format(value, self.type)
|
||||
)
|
||||
"Failed to cast value {value} to type {type}", value, self.type)
|
||||
|
||||
def unmarshal(self, value, custom_formatters=None):
|
||||
"""Unmarshal parameter from the value."""
|
||||
|
@ -188,9 +187,7 @@ class Schema(object):
|
|||
|
||||
if self.enum and casted not in self.enum:
|
||||
raise InvalidSchemaValue(
|
||||
"Value of {0} not in enum choices: {1}".format(
|
||||
value, self.enum)
|
||||
)
|
||||
"Value {value} not in enum choices: {type}", value, self.enum)
|
||||
|
||||
return casted
|
||||
|
||||
|
@ -198,23 +195,21 @@ class Schema(object):
|
|||
try:
|
||||
schema_format = SchemaFormat(self.format)
|
||||
except ValueError:
|
||||
msg = "Unsupported {0} format unmarshalling".format(self.format)
|
||||
msg = "Unsupported format {type} unmarshalling for value {value}"
|
||||
if custom_formatters is not None:
|
||||
formatstring = custom_formatters.get(self.format)
|
||||
if formatstring is None:
|
||||
raise OpenAPISchemaError(msg)
|
||||
raise InvalidSchemaValue(msg, value, self.format)
|
||||
else:
|
||||
raise OpenAPISchemaError(msg)
|
||||
raise InvalidSchemaValue(msg, value, self.format)
|
||||
else:
|
||||
formatstring = self.STRING_FORMAT_CALLABLE_GETTER[schema_format]
|
||||
|
||||
try:
|
||||
return formatstring.unmarshal(value)
|
||||
except ValueError:
|
||||
raise InvalidSchemaValue(
|
||||
"Failed to format value of {0} to {1}".format(
|
||||
value, self.format)
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise InvalidCustomFormatSchemaValue(
|
||||
"Failed to format value {value} to format {type}: {exception}", value, self.format, exc)
|
||||
|
||||
def _unmarshal_any(self, value, custom_formatters=None):
|
||||
types_resolve_order = [
|
||||
|
@ -230,12 +225,11 @@ class Schema(object):
|
|||
except (OpenAPISchemaError, TypeError, ValueError):
|
||||
continue
|
||||
|
||||
raise NoValidSchema(
|
||||
"No valid schema found for value {0}".format(value))
|
||||
raise NoValidSchema(value)
|
||||
|
||||
def _unmarshal_collection(self, value, custom_formatters=None):
|
||||
if self.items is None:
|
||||
raise UndefinedItemsSchema("Undefined items' schema")
|
||||
raise UndefinedItemsSchema(self.type)
|
||||
|
||||
f = functools.partial(self.items.unmarshal,
|
||||
custom_formatters=custom_formatters)
|
||||
|
@ -244,8 +238,7 @@ class Schema(object):
|
|||
def _unmarshal_object(self, value, model_factory=None,
|
||||
custom_formatters=None):
|
||||
if not isinstance(value, (dict, )):
|
||||
raise InvalidSchemaValue(
|
||||
"Value of {0} not a dict".format(value))
|
||||
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
|
||||
|
||||
model_factory = model_factory or ModelFactory()
|
||||
|
||||
|
@ -259,14 +252,11 @@ class Schema(object):
|
|||
pass
|
||||
else:
|
||||
if properties is not None:
|
||||
raise MultipleOneOfSchema(
|
||||
"Exactly one schema should be valid,"
|
||||
"multiple found")
|
||||
raise MultipleOneOfSchema(self.type)
|
||||
properties = found_props
|
||||
|
||||
if properties is None:
|
||||
raise NoOneOfSchema(
|
||||
"Exactly one valid schema should be valid, None found.")
|
||||
raise NoOneOfSchema(self.type)
|
||||
|
||||
else:
|
||||
properties = self._unmarshal_properties(
|
||||
|
@ -290,8 +280,7 @@ class Schema(object):
|
|||
value_props_names = value.keys()
|
||||
extra_props = set(value_props_names) - set(all_props_names)
|
||||
if extra_props and self.additional_properties is None:
|
||||
raise UndefinedSchemaProperty(
|
||||
"Undefined properties in schema: {0}".format(extra_props))
|
||||
raise UndefinedSchemaProperty(extra_props)
|
||||
|
||||
properties = {}
|
||||
for prop_name in extra_props:
|
||||
|
@ -304,8 +293,7 @@ class Schema(object):
|
|||
prop_value = value[prop_name]
|
||||
except KeyError:
|
||||
if prop_name in all_req_props_names:
|
||||
raise MissingSchemaProperty(
|
||||
"Missing schema property {0}".format(prop_name))
|
||||
raise MissingSchemaProperty(prop_name)
|
||||
if not prop.nullable and not prop.default:
|
||||
continue
|
||||
prop_value = prop.default
|
||||
|
@ -334,7 +322,7 @@ class Schema(object):
|
|||
def validate(self, value, custom_formatters=None):
|
||||
if value is None:
|
||||
if not self.nullable:
|
||||
raise InvalidSchemaValue("Null value for non-nullable schema")
|
||||
raise InvalidSchemaValue("Null value for non-nullable schema of type {type}", value, self.type)
|
||||
return
|
||||
|
||||
# type validation
|
||||
|
@ -342,9 +330,7 @@ class Schema(object):
|
|||
self.type]
|
||||
if not type_validator_callable(value):
|
||||
raise InvalidSchemaValue(
|
||||
"Value of {0} not valid type of {1}".format(
|
||||
value, self.type.value)
|
||||
)
|
||||
"Value {value} not valid type {type}", value, self.type.value)
|
||||
|
||||
# structure validation
|
||||
validator_mapping = self.get_validator_mapping()
|
||||
|
@ -355,7 +341,7 @@ class Schema(object):
|
|||
|
||||
def _validate_collection(self, value, custom_formatters=None):
|
||||
if self.items is None:
|
||||
raise OpenAPISchemaError("Schema for collection not defined")
|
||||
raise UndefinedItemsSchema(self.type)
|
||||
|
||||
if self.min_items is not None:
|
||||
if self.min_items < 0:
|
||||
|
@ -436,9 +422,7 @@ class Schema(object):
|
|||
|
||||
if not formatstring.validate(value):
|
||||
raise InvalidSchemaValue(
|
||||
"Value of {0} not valid format of {1}".format(
|
||||
value, self.format)
|
||||
)
|
||||
"Value {value} not valid format {type}", value, self.format)
|
||||
|
||||
if self.min_length is not None:
|
||||
if self.min_length < 0:
|
||||
|
@ -484,14 +468,11 @@ class Schema(object):
|
|||
pass
|
||||
else:
|
||||
if valid_one_of_schema is not None:
|
||||
raise MultipleOneOfSchema(
|
||||
"Exactly one schema should be valid,"
|
||||
"multiple found")
|
||||
raise MultipleOneOfSchema(self.type)
|
||||
valid_one_of_schema = True
|
||||
|
||||
if valid_one_of_schema is None:
|
||||
raise NoOneOfSchema(
|
||||
"Exactly one valid schema should be valid, None found.")
|
||||
raise NoOneOfSchema(self.type)
|
||||
|
||||
else:
|
||||
self._validate_properties(properties,
|
||||
|
@ -542,8 +523,7 @@ class Schema(object):
|
|||
value_props_names = value.keys()
|
||||
extra_props = set(value_props_names) - set(all_props_names)
|
||||
if extra_props and self.additional_properties is None:
|
||||
raise UndefinedSchemaProperty(
|
||||
"Undefined properties in schema: {0}".format(extra_props))
|
||||
raise UndefinedSchemaProperty(extra_props)
|
||||
|
||||
for prop_name in extra_props:
|
||||
prop_value = value[prop_name]
|
||||
|
@ -555,8 +535,7 @@ class Schema(object):
|
|||
prop_value = value[prop_name]
|
||||
except KeyError:
|
||||
if prop_name in all_req_props_names:
|
||||
raise MissingSchemaProperty(
|
||||
"Missing schema property {0}".format(prop_name))
|
||||
raise MissingSchemaProperty(prop_name)
|
||||
if not prop.nullable and not prop.default:
|
||||
continue
|
||||
prop_value = prop.default
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
from openapi_core.schema.exceptions import OpenAPIMappingError
|
||||
|
||||
import attr
|
||||
|
||||
|
||||
class OpenAPIServerError(OpenAPIMappingError):
|
||||
pass
|
||||
|
||||
|
||||
@attr.s
|
||||
class InvalidServer(OpenAPIServerError):
|
||||
pass
|
||||
full_url_pattern = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
return "Invalid request server {0}".format(
|
||||
self.full_url_pattern)
|
||||
|
|
|
@ -31,8 +31,7 @@ class Spec(object):
|
|||
if spec_server.default_url in full_url_pattern:
|
||||
return spec_server
|
||||
|
||||
raise InvalidServer(
|
||||
"Invalid request server {0}".format(full_url_pattern))
|
||||
raise InvalidServer(full_url_pattern)
|
||||
|
||||
def get_server_url(self, index=0):
|
||||
return self.servers[index].default_url
|
||||
|
@ -41,9 +40,7 @@ class Spec(object):
|
|||
try:
|
||||
return self.paths[path_pattern].operations[http_method]
|
||||
except KeyError:
|
||||
raise InvalidOperation(
|
||||
"Unknown operation path {0} with method {1}".format(
|
||||
path_pattern, http_method))
|
||||
raise InvalidOperation(path_pattern, http_method)
|
||||
|
||||
def get_schema(self, name):
|
||||
return self.components.schemas[name]
|
||||
|
|
Loading…
Reference in a new issue