mirror of
https://github.com/correl/openapi-core.git
synced 2024-11-28 11:09:52 +00:00
Merge pull request #94 from domenkozar/structured-exceptions
Structured exceptions
This commit is contained in:
commit
2e6f2dedc9
22 changed files with 305 additions and 204 deletions
|
@ -1,9 +1,16 @@
|
||||||
from openapi_core.schema.exceptions import OpenAPIMappingError
|
from openapi_core.schema.exceptions import OpenAPIMappingError
|
||||||
|
|
||||||
|
import attr
|
||||||
|
|
||||||
|
|
||||||
class OpenAPIContentError(OpenAPIMappingError):
|
class OpenAPIContentError(OpenAPIMappingError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
class MimeTypeNotFound(OpenAPIContentError):
|
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):
|
if fnmatch.fnmatch(mimetype, key):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
raise MimeTypeNotFound("{0} mimetype not found")
|
raise MimeTypeNotFound(mimetype, self.keys())
|
||||||
|
|
|
@ -1,13 +1,21 @@
|
||||||
from openapi_core.schema.exceptions import OpenAPIMappingError
|
from openapi_core.schema.exceptions import OpenAPIMappingError
|
||||||
|
|
||||||
|
import attr
|
||||||
|
|
||||||
|
|
||||||
class OpenAPIMediaTypeError(OpenAPIMappingError):
|
class OpenAPIMediaTypeError(OpenAPIMappingError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@attr.s
|
||||||
class InvalidMediaTypeValue(OpenAPIMediaTypeError):
|
class InvalidMediaTypeValue(OpenAPIMediaTypeError):
|
||||||
pass
|
original_exception = attr.ib()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Mimetype invalid: {0}".format(self.original_exception)
|
||||||
|
|
||||||
|
@attr.s
|
||||||
class InvalidContentType(OpenAPIMediaTypeError):
|
class InvalidContentType(OpenAPIMediaTypeError):
|
||||||
pass
|
mimetype = attr.ib()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Content for following mimetype not found: {0}".format(self.mimetype)
|
||||||
|
|
|
@ -4,7 +4,7 @@ from collections import defaultdict
|
||||||
from json import loads
|
from json import loads
|
||||||
|
|
||||||
from openapi_core.schema.media_types.exceptions import InvalidMediaTypeValue
|
from openapi_core.schema.media_types.exceptions import InvalidMediaTypeValue
|
||||||
from openapi_core.schema.schemas.exceptions import InvalidSchemaValue
|
from openapi_core.schema.schemas.exceptions import OpenAPISchemaError
|
||||||
|
|
||||||
|
|
||||||
MEDIA_TYPE_DESERIALIZERS = {
|
MEDIA_TYPE_DESERIALIZERS = {
|
||||||
|
@ -32,21 +32,21 @@ class MediaType(object):
|
||||||
deserializer = self.get_dererializer()
|
deserializer = self.get_dererializer()
|
||||||
return deserializer(value)
|
return deserializer(value)
|
||||||
|
|
||||||
def unmarshal(self, value):
|
def unmarshal(self, value, custom_formatters=None):
|
||||||
if not self.schema:
|
if not self.schema:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
try:
|
try:
|
||||||
deserialized = self.deserialize(value)
|
deserialized = self.deserialize(value)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise InvalidMediaTypeValue(str(exc))
|
raise InvalidMediaTypeValue(exc)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
unmarshalled = self.schema.unmarshal(deserialized)
|
unmarshalled = self.schema.unmarshal(deserialized, custom_formatters=custom_formatters)
|
||||||
except InvalidSchemaValue as exc:
|
except OpenAPISchemaError as exc:
|
||||||
raise InvalidMediaTypeValue(str(exc))
|
raise InvalidMediaTypeValue(exc)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.schema.validate(unmarshalled)
|
return self.schema.validate(unmarshalled, custom_formatters=custom_formatters)
|
||||||
except InvalidSchemaValue as exc:
|
except OpenAPISchemaError as exc:
|
||||||
raise InvalidMediaTypeValue(str(exc))
|
raise InvalidMediaTypeValue(exc)
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
from openapi_core.schema.exceptions import OpenAPIMappingError
|
from openapi_core.schema.exceptions import OpenAPIMappingError
|
||||||
|
|
||||||
|
import attr
|
||||||
|
|
||||||
|
|
||||||
class OpenAPIOperationError(OpenAPIMappingError):
|
class OpenAPIOperationError(OpenAPIMappingError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
class InvalidOperation(OpenAPIOperationError):
|
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]
|
return self.responses[http_status_range]
|
||||||
|
|
||||||
if 'default' not in self.responses:
|
if 'default' not in self.responses:
|
||||||
raise InvalidResponse(
|
raise InvalidResponse(http_status, self.responses)
|
||||||
"Unknown response http status {0}".format(http_status))
|
|
||||||
|
|
||||||
return self.responses['default']
|
return self.responses['default']
|
||||||
|
|
|
@ -1,21 +1,40 @@
|
||||||
from openapi_core.schema.exceptions import OpenAPIMappingError
|
from openapi_core.schema.exceptions import OpenAPIMappingError
|
||||||
|
|
||||||
|
import attr
|
||||||
|
|
||||||
|
|
||||||
class OpenAPIParameterError(OpenAPIMappingError):
|
class OpenAPIParameterError(OpenAPIMappingError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
class MissingParameter(OpenAPIParameterError):
|
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):
|
class MissingRequiredParameter(OpenAPIParameterError):
|
||||||
pass
|
name = attr.ib()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Missing required parameter: {0}".format(self.name)
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
class EmptyParameterValue(OpenAPIParameterError):
|
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):
|
class InvalidParameterValue(OpenAPIParameterError):
|
||||||
pass
|
name = attr.ib()
|
||||||
|
original_exception = attr.ib()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Invalid parameter value for `{0}`: {1}".format(self.name, self.original_exception)
|
||||||
|
|
|
@ -10,7 +10,7 @@ from openapi_core.schema.parameters.exceptions import (
|
||||||
EmptyParameterValue,
|
EmptyParameterValue,
|
||||||
)
|
)
|
||||||
from openapi_core.schema.schemas.enums import SchemaType
|
from openapi_core.schema.schemas.enums import SchemaType
|
||||||
from openapi_core.schema.schemas.exceptions import InvalidSchemaValue
|
from openapi_core.schema.schemas.exceptions import OpenAPISchemaError
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -77,12 +77,10 @@ class Parameter(object):
|
||||||
|
|
||||||
if self.name not in location:
|
if self.name not in location:
|
||||||
if self.required:
|
if self.required:
|
||||||
raise MissingRequiredParameter(
|
raise MissingRequiredParameter(self.name)
|
||||||
"Missing required `{0}` parameter".format(self.name))
|
|
||||||
|
|
||||||
if not self.schema or self.schema.default is None:
|
if not self.schema or self.schema.default is None:
|
||||||
raise MissingParameter(
|
raise MissingParameter(self.name)
|
||||||
"Missing `{0}` parameter".format(self.name))
|
|
||||||
|
|
||||||
return self.schema.default
|
return self.schema.default
|
||||||
|
|
||||||
|
@ -91,7 +89,7 @@ class Parameter(object):
|
||||||
|
|
||||||
return location[self.name]
|
return location[self.name]
|
||||||
|
|
||||||
def unmarshal(self, value):
|
def unmarshal(self, value, custom_formatters=None):
|
||||||
if self.deprecated:
|
if self.deprecated:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"{0} parameter is deprecated".format(self.name),
|
"{0} parameter is deprecated".format(self.name),
|
||||||
|
@ -100,8 +98,7 @@ class Parameter(object):
|
||||||
|
|
||||||
if (self.location == ParameterLocation.QUERY and value == "" and
|
if (self.location == ParameterLocation.QUERY and value == "" and
|
||||||
not self.allow_empty_value):
|
not self.allow_empty_value):
|
||||||
raise EmptyParameterValue(
|
raise EmptyParameterValue(self.name)
|
||||||
"Value of {0} parameter cannot be empty".format(self.name))
|
|
||||||
|
|
||||||
if not self.schema:
|
if not self.schema:
|
||||||
return value
|
return value
|
||||||
|
@ -109,14 +106,14 @@ class Parameter(object):
|
||||||
try:
|
try:
|
||||||
deserialized = self.deserialize(value)
|
deserialized = self.deserialize(value)
|
||||||
except (ValueError, AttributeError) as exc:
|
except (ValueError, AttributeError) as exc:
|
||||||
raise InvalidParameterValue(str(exc))
|
raise InvalidParameterValue(self.name, exc)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
unmarshalled = self.schema.unmarshal(deserialized)
|
unmarshalled = self.schema.unmarshal(deserialized, custom_formatters=custom_formatters)
|
||||||
except InvalidSchemaValue as exc:
|
except OpenAPISchemaError as exc:
|
||||||
raise InvalidParameterValue(str(exc))
|
raise InvalidParameterValue(self.name, exc)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.schema.validate(unmarshalled)
|
return self.schema.validate(unmarshalled, custom_formatters=custom_formatters)
|
||||||
except InvalidSchemaValue as exc:
|
except OpenAPISchemaError as exc:
|
||||||
raise InvalidParameterValue(str(exc))
|
raise InvalidParameterValue(self.name, exc)
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
from openapi_core.schema.exceptions import OpenAPIMappingError
|
from openapi_core.schema.exceptions import OpenAPIMappingError
|
||||||
|
|
||||||
|
import attr
|
||||||
|
|
||||||
|
|
||||||
class OpenAPIRequestBodyError(OpenAPIMappingError):
|
class OpenAPIRequestBodyError(OpenAPIMappingError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
class MissingRequestBody(OpenAPIRequestBodyError):
|
class MissingRequestBody(OpenAPIRequestBodyError):
|
||||||
pass
|
request = attr.ib()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Missing required request body"
|
||||||
|
|
|
@ -16,11 +16,9 @@ class RequestBody(object):
|
||||||
try:
|
try:
|
||||||
return self.content[mimetype]
|
return self.content[mimetype]
|
||||||
except MimeTypeNotFound:
|
except MimeTypeNotFound:
|
||||||
raise InvalidContentType(
|
raise InvalidContentType(mimetype)
|
||||||
"Invalid mime type `{0}`".format(mimetype))
|
|
||||||
|
|
||||||
def get_value(self, request):
|
def get_value(self, request):
|
||||||
if not request.body and self.required:
|
if not request.body and self.required:
|
||||||
raise MissingRequestBody("Missing required request body")
|
raise MissingRequestBody(request)
|
||||||
|
|
||||||
return request.body
|
return request.body
|
||||||
|
|
|
@ -1,13 +1,24 @@
|
||||||
from openapi_core.schema.exceptions import OpenAPIMappingError
|
from openapi_core.schema.exceptions import OpenAPIMappingError
|
||||||
|
|
||||||
|
import attr
|
||||||
|
|
||||||
|
|
||||||
class OpenAPIResponseError(OpenAPIMappingError):
|
class OpenAPIResponseError(OpenAPIMappingError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
class InvalidResponse(OpenAPIResponseError):
|
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):
|
class MissingResponseContent(OpenAPIResponseError):
|
||||||
pass
|
response = attr.ib()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Missing response content"
|
||||||
|
|
|
@ -23,11 +23,10 @@ class Response(object):
|
||||||
try:
|
try:
|
||||||
return self.content[mimetype]
|
return self.content[mimetype]
|
||||||
except MimeTypeNotFound:
|
except MimeTypeNotFound:
|
||||||
raise InvalidContentType(
|
raise InvalidContentType(mimetype)
|
||||||
"Invalid mime type `{0}`".format(mimetype))
|
|
||||||
|
|
||||||
def get_value(self, response):
|
def get_value(self, response):
|
||||||
if not response.data:
|
if not response.data:
|
||||||
raise MissingResponseContent("Missing response content")
|
raise MissingResponseContent(response)
|
||||||
|
|
||||||
return response.data
|
return response.data
|
||||||
|
|
|
@ -1,33 +1,79 @@
|
||||||
from openapi_core.schema.exceptions import OpenAPIMappingError
|
from openapi_core.schema.exceptions import OpenAPIMappingError
|
||||||
|
|
||||||
|
import attr
|
||||||
|
|
||||||
|
|
||||||
class OpenAPISchemaError(OpenAPIMappingError):
|
class OpenAPISchemaError(OpenAPIMappingError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
class NoValidSchema(OpenAPISchemaError):
|
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):
|
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):
|
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):
|
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 InvalidSchemaProperty(OpenAPISchemaError):
|
||||||
|
property_name = attr.ib()
|
||||||
|
original_exception = attr.ib()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Invalid schema property {0}: {1}".format(self.property_name, self.original_exception)
|
||||||
|
|
||||||
|
@attr.s
|
||||||
class MissingSchemaProperty(OpenAPISchemaError):
|
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):
|
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):
|
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)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
"""OpenAPI core schemas models module"""
|
"""OpenAPI core schemas models module"""
|
||||||
|
import attr
|
||||||
|
import functools
|
||||||
import logging
|
import logging
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
|
@ -12,7 +14,7 @@ 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, NoOneOfSchema, MultipleOneOfSchema, NoValidSchema,
|
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema, NoValidSchema,
|
||||||
UndefinedItemsSchema,
|
UndefinedItemsSchema, InvalidCustomFormatSchemaValue, InvalidSchemaProperty,
|
||||||
)
|
)
|
||||||
from openapi_core.schema.schemas.util import (
|
from openapi_core.schema.schemas.util import (
|
||||||
forcebool, format_date, format_datetime,
|
forcebool, format_date, format_datetime,
|
||||||
|
@ -24,6 +26,12 @@ from openapi_core.schema.schemas.validators import (
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
|
class Format(object):
|
||||||
|
unmarshal = attr.ib()
|
||||||
|
validate = attr.ib()
|
||||||
|
|
||||||
|
|
||||||
class Schema(object):
|
class Schema(object):
|
||||||
"""Represents an OpenAPI Schema."""
|
"""Represents an OpenAPI Schema."""
|
||||||
|
|
||||||
|
@ -33,18 +41,11 @@ class Schema(object):
|
||||||
SchemaType.BOOLEAN: forcebool,
|
SchemaType.BOOLEAN: forcebool,
|
||||||
}
|
}
|
||||||
|
|
||||||
STRING_FORMAT_CAST_CALLABLE_GETTER = {
|
STRING_FORMAT_CALLABLE_GETTER = {
|
||||||
SchemaFormat.NONE: text_type,
|
SchemaFormat.NONE: Format(text_type, TypeValidator(text_type)),
|
||||||
SchemaFormat.DATE: format_date,
|
SchemaFormat.DATE: Format(format_date, TypeValidator(date, exclude=datetime)),
|
||||||
SchemaFormat.DATETIME: format_datetime,
|
SchemaFormat.DATETIME: Format(format_datetime, TypeValidator(datetime)),
|
||||||
SchemaFormat.BINARY: binary_type,
|
SchemaFormat.BINARY: Format(binary_type, TypeValidator(binary_type)),
|
||||||
}
|
|
||||||
|
|
||||||
STRING_FORMAT_VALIDATOR_CALLABLE_GETTER = {
|
|
||||||
SchemaFormat.NONE: TypeValidator(text_type),
|
|
||||||
SchemaFormat.DATE: TypeValidator(date, exclude=datetime),
|
|
||||||
SchemaFormat.DATETIME: TypeValidator(datetime),
|
|
||||||
SchemaFormat.BINARY: TypeValidator(binary_type),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TYPE_VALIDATOR_CALLABLE_GETTER = {
|
TYPE_VALIDATOR_CALLABLE_GETTER = {
|
||||||
|
@ -142,25 +143,27 @@ class Schema(object):
|
||||||
|
|
||||||
return set(required)
|
return set(required)
|
||||||
|
|
||||||
def get_cast_mapping(self):
|
def get_cast_mapping(self, custom_formatters=None):
|
||||||
|
pass_defaults = lambda f: functools.partial(
|
||||||
|
f, custom_formatters=custom_formatters)
|
||||||
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy()
|
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy()
|
||||||
mapping.update({
|
mapping.update({
|
||||||
SchemaType.STRING: self._unmarshal_string,
|
SchemaType.STRING: pass_defaults(self._unmarshal_string),
|
||||||
SchemaType.ANY: self._unmarshal_any,
|
SchemaType.ANY: pass_defaults(self._unmarshal_any),
|
||||||
SchemaType.ARRAY: self._unmarshal_collection,
|
SchemaType.ARRAY: pass_defaults(self._unmarshal_collection),
|
||||||
SchemaType.OBJECT: self._unmarshal_object,
|
SchemaType.OBJECT: pass_defaults(self._unmarshal_object),
|
||||||
})
|
})
|
||||||
|
|
||||||
return defaultdict(lambda: lambda x: x, mapping)
|
return defaultdict(lambda: lambda x: x, mapping)
|
||||||
|
|
||||||
def cast(self, value):
|
def cast(self, value, custom_formatters=None):
|
||||||
"""Cast value to schema type"""
|
"""Cast value to schema type"""
|
||||||
if value is None:
|
if value is None:
|
||||||
if not self.nullable:
|
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
|
return self.default
|
||||||
|
|
||||||
cast_mapping = self.get_cast_mapping()
|
cast_mapping = self.get_cast_mapping(custom_formatters=custom_formatters)
|
||||||
|
|
||||||
if self.type is not SchemaType.STRING and value == '':
|
if self.type is not SchemaType.STRING and value == '':
|
||||||
return None
|
return None
|
||||||
|
@ -170,47 +173,45 @@ class Schema(object):
|
||||||
return cast_callable(value)
|
return cast_callable(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise InvalidSchemaValue(
|
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):
|
def unmarshal(self, value, custom_formatters=None):
|
||||||
"""Unmarshal parameter from the value."""
|
"""Unmarshal parameter from the value."""
|
||||||
if self.deprecated:
|
if self.deprecated:
|
||||||
warnings.warn("The schema is deprecated", DeprecationWarning)
|
warnings.warn("The schema is deprecated", DeprecationWarning)
|
||||||
|
|
||||||
casted = self.cast(value)
|
casted = self.cast(value, custom_formatters=custom_formatters)
|
||||||
|
|
||||||
if casted is None and not self.required:
|
if casted is None and not self.required:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if self.enum and casted not in self.enum:
|
if self.enum and casted not in self.enum:
|
||||||
raise InvalidSchemaValue(
|
raise InvalidSchemaValue(
|
||||||
"Value of {0} not in enum choices: {1}".format(
|
"Value {value} not in enum choices: {type}", value, self.enum)
|
||||||
value, self.enum)
|
|
||||||
)
|
|
||||||
|
|
||||||
return casted
|
return casted
|
||||||
|
|
||||||
def _unmarshal_string(self, value):
|
def _unmarshal_string(self, value, custom_formatters=None):
|
||||||
try:
|
try:
|
||||||
schema_format = SchemaFormat(self.format)
|
schema_format = SchemaFormat(self.format)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# @todo: implement custom format unmarshalling support
|
msg = "Unsupported format {type} unmarshalling for value {value}"
|
||||||
raise OpenAPISchemaError(
|
if custom_formatters is not None:
|
||||||
"Unsupported {0} format unmarshalling".format(self.format)
|
formatstring = custom_formatters.get(self.format)
|
||||||
)
|
if formatstring is None:
|
||||||
|
raise InvalidSchemaValue(msg, value, self.format)
|
||||||
|
else:
|
||||||
|
raise InvalidSchemaValue(msg, value, self.format)
|
||||||
else:
|
else:
|
||||||
formatter = self.STRING_FORMAT_CAST_CALLABLE_GETTER[schema_format]
|
formatstring = self.STRING_FORMAT_CALLABLE_GETTER[schema_format]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return formatter(value)
|
return formatstring.unmarshal(value)
|
||||||
except ValueError:
|
except ValueError as exc:
|
||||||
raise InvalidSchemaValue(
|
raise InvalidCustomFormatSchemaValue(
|
||||||
"Failed to format value of {0} to {1}".format(
|
"Failed to format value {value} to format {type}: {exception}", value, self.format, exc)
|
||||||
value, self.format)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _unmarshal_any(self, value):
|
def _unmarshal_any(self, value, custom_formatters=None):
|
||||||
types_resolve_order = [
|
types_resolve_order = [
|
||||||
SchemaType.OBJECT, SchemaType.ARRAY, SchemaType.BOOLEAN,
|
SchemaType.OBJECT, SchemaType.ARRAY, SchemaType.BOOLEAN,
|
||||||
SchemaType.INTEGER, SchemaType.NUMBER, SchemaType.STRING,
|
SchemaType.INTEGER, SchemaType.NUMBER, SchemaType.STRING,
|
||||||
|
@ -224,19 +225,20 @@ class Schema(object):
|
||||||
except (OpenAPISchemaError, TypeError, ValueError):
|
except (OpenAPISchemaError, TypeError, ValueError):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
raise NoValidSchema(
|
raise NoValidSchema(value)
|
||||||
"No valid schema found for value {0}".format(value))
|
|
||||||
|
|
||||||
def _unmarshal_collection(self, value):
|
def _unmarshal_collection(self, value, custom_formatters=None):
|
||||||
if self.items is None:
|
if self.items is None:
|
||||||
raise UndefinedItemsSchema("Undefined items' schema")
|
raise UndefinedItemsSchema(self.type)
|
||||||
|
|
||||||
return list(map(self.items.unmarshal, value))
|
f = functools.partial(self.items.unmarshal,
|
||||||
|
custom_formatters=custom_formatters)
|
||||||
|
return list(map(f, value))
|
||||||
|
|
||||||
def _unmarshal_object(self, value, model_factory=None):
|
def _unmarshal_object(self, value, model_factory=None,
|
||||||
|
custom_formatters=None):
|
||||||
if not isinstance(value, (dict, )):
|
if not isinstance(value, (dict, )):
|
||||||
raise InvalidSchemaValue(
|
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
|
||||||
"Value of {0} not a dict".format(value))
|
|
||||||
|
|
||||||
model_factory = model_factory or ModelFactory()
|
model_factory = model_factory or ModelFactory()
|
||||||
|
|
||||||
|
@ -245,26 +247,25 @@ class Schema(object):
|
||||||
for one_of_schema in self.one_of:
|
for one_of_schema in self.one_of:
|
||||||
try:
|
try:
|
||||||
found_props = self._unmarshal_properties(
|
found_props = self._unmarshal_properties(
|
||||||
value, one_of_schema)
|
value, one_of_schema, custom_formatters=custom_formatters)
|
||||||
except OpenAPISchemaError:
|
except OpenAPISchemaError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if properties is not None:
|
if properties is not None:
|
||||||
raise MultipleOneOfSchema(
|
raise MultipleOneOfSchema(self.type)
|
||||||
"Exactly one schema should be valid,"
|
|
||||||
"multiple found")
|
|
||||||
properties = found_props
|
properties = found_props
|
||||||
|
|
||||||
if properties is None:
|
if properties is None:
|
||||||
raise NoOneOfSchema(
|
raise NoOneOfSchema(self.type)
|
||||||
"Exactly one valid schema should be valid, None found.")
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
properties = self._unmarshal_properties(value)
|
properties = self._unmarshal_properties(
|
||||||
|
value, custom_formatters=custom_formatters)
|
||||||
|
|
||||||
return model_factory.create(properties, name=self.model)
|
return model_factory.create(properties, name=self.model)
|
||||||
|
|
||||||
def _unmarshal_properties(self, value, one_of_schema=None):
|
def _unmarshal_properties(self, value, one_of_schema=None,
|
||||||
|
custom_formatters=None):
|
||||||
all_props = self.get_all_properties()
|
all_props = self.get_all_properties()
|
||||||
all_props_names = self.get_all_properties_names()
|
all_props_names = self.get_all_properties_names()
|
||||||
all_req_props_names = self.get_all_required_properties_names()
|
all_req_props_names = self.get_all_required_properties_names()
|
||||||
|
@ -279,28 +280,31 @@ class Schema(object):
|
||||||
value_props_names = value.keys()
|
value_props_names = value.keys()
|
||||||
extra_props = set(value_props_names) - set(all_props_names)
|
extra_props = set(value_props_names) - set(all_props_names)
|
||||||
if extra_props and self.additional_properties is None:
|
if extra_props and self.additional_properties is None:
|
||||||
raise UndefinedSchemaProperty(
|
raise UndefinedSchemaProperty(extra_props)
|
||||||
"Undefined properties in schema: {0}".format(extra_props))
|
|
||||||
|
|
||||||
properties = {}
|
properties = {}
|
||||||
for prop_name in extra_props:
|
for prop_name in extra_props:
|
||||||
prop_value = value[prop_name]
|
prop_value = value[prop_name]
|
||||||
properties[prop_name] = self.additional_properties.unmarshal(
|
properties[prop_name] = self.additional_properties.unmarshal(
|
||||||
prop_value)
|
prop_value, custom_formatters=custom_formatters)
|
||||||
|
|
||||||
for prop_name, prop in iteritems(all_props):
|
for prop_name, prop in iteritems(all_props):
|
||||||
try:
|
try:
|
||||||
prop_value = value[prop_name]
|
prop_value = value[prop_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if prop_name in all_req_props_names:
|
if prop_name in all_req_props_names:
|
||||||
raise MissingSchemaProperty(
|
raise MissingSchemaProperty(prop_name)
|
||||||
"Missing schema property {0}".format(prop_name))
|
|
||||||
if not prop.nullable and not prop.default:
|
if not prop.nullable and not prop.default:
|
||||||
continue
|
continue
|
||||||
prop_value = prop.default
|
prop_value = prop.default
|
||||||
properties[prop_name] = prop.unmarshal(prop_value)
|
try:
|
||||||
|
properties[prop_name] = prop.unmarshal(
|
||||||
|
prop_value, custom_formatters=custom_formatters)
|
||||||
|
except OpenAPISchemaError as exc:
|
||||||
|
raise InvalidSchemaProperty(prop_name, exc)
|
||||||
|
|
||||||
self._validate_properties(properties, one_of_schema=one_of_schema)
|
self._validate_properties(properties, one_of_schema=one_of_schema,
|
||||||
|
custom_formatters=custom_formatters)
|
||||||
|
|
||||||
return properties
|
return properties
|
||||||
|
|
||||||
|
@ -313,12 +317,15 @@ class Schema(object):
|
||||||
SchemaType.NUMBER: self._validate_number,
|
SchemaType.NUMBER: self._validate_number,
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaultdict(lambda: lambda x: x, mapping)
|
def default(x, **kw):
|
||||||
|
return x
|
||||||
|
|
||||||
def validate(self, value):
|
return defaultdict(lambda: default, mapping)
|
||||||
|
|
||||||
|
def validate(self, value, custom_formatters=None):
|
||||||
if value is None:
|
if value is None:
|
||||||
if not self.nullable:
|
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
|
return
|
||||||
|
|
||||||
# type validation
|
# type validation
|
||||||
|
@ -326,20 +333,18 @@ class Schema(object):
|
||||||
self.type]
|
self.type]
|
||||||
if not type_validator_callable(value):
|
if not type_validator_callable(value):
|
||||||
raise InvalidSchemaValue(
|
raise InvalidSchemaValue(
|
||||||
"Value of {0} not valid type of {1}".format(
|
"Value {value} not valid type {type}", value, self.type.value)
|
||||||
value, self.type.value)
|
|
||||||
)
|
|
||||||
|
|
||||||
# structure validation
|
# structure validation
|
||||||
validator_mapping = self.get_validator_mapping()
|
validator_mapping = self.get_validator_mapping()
|
||||||
validator_callable = validator_mapping[self.type]
|
validator_callable = validator_mapping[self.type]
|
||||||
validator_callable(value)
|
validator_callable(value, custom_formatters=custom_formatters)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def _validate_collection(self, value):
|
def _validate_collection(self, value, custom_formatters=None):
|
||||||
if self.items is 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 is not None:
|
||||||
if self.min_items < 0:
|
if self.min_items < 0:
|
||||||
|
@ -349,10 +354,8 @@ class Schema(object):
|
||||||
)
|
)
|
||||||
if len(value) < self.min_items:
|
if len(value) < self.min_items:
|
||||||
raise InvalidSchemaValue(
|
raise InvalidSchemaValue(
|
||||||
"Value must contain at least {0} item(s),"
|
"Value must contain at least {type} item(s),"
|
||||||
" {1} found".format(
|
" {value} found", len(value), self.min_items)
|
||||||
self.min_items, len(value))
|
|
||||||
)
|
|
||||||
if self.max_items is not None:
|
if self.max_items is not None:
|
||||||
if self.max_items < 0:
|
if self.max_items < 0:
|
||||||
raise OpenAPISchemaError(
|
raise OpenAPISchemaError(
|
||||||
|
@ -361,63 +364,55 @@ class Schema(object):
|
||||||
)
|
)
|
||||||
if len(value) > self.max_items:
|
if len(value) > self.max_items:
|
||||||
raise InvalidSchemaValue(
|
raise InvalidSchemaValue(
|
||||||
"Value must contain at most {0} item(s),"
|
"Value must contain at most {value} item(s),"
|
||||||
" {1} found".format(
|
" {type} found", len(value), self.max_items)
|
||||||
self.max_items, len(value))
|
|
||||||
)
|
|
||||||
if self.unique_items and len(set(value)) != len(value):
|
if self.unique_items and len(set(value)) != len(value):
|
||||||
raise InvalidSchemaValue("Value may not contain duplicate items")
|
raise OpenAPISchemaError("Value may not contain duplicate items")
|
||||||
|
|
||||||
return list(map(self.items.validate, value))
|
f = functools.partial(self.items.validate,
|
||||||
|
custom_formatters=custom_formatters)
|
||||||
|
return list(map(f, value))
|
||||||
|
|
||||||
def _validate_number(self, value):
|
def _validate_number(self, value, custom_formatters=None):
|
||||||
if self.minimum is not None:
|
if self.minimum is not None:
|
||||||
if self.exclusive_minimum and value <= self.minimum:
|
if self.exclusive_minimum and value <= self.minimum:
|
||||||
raise InvalidSchemaValue(
|
raise InvalidSchemaValue(
|
||||||
"Value {0} is not less than or equal to {1}".format(
|
"Value {value} is not less than or equal to {type}", value, self.minimum)
|
||||||
value, self.minimum)
|
|
||||||
)
|
|
||||||
elif value < self.minimum:
|
elif value < self.minimum:
|
||||||
raise InvalidSchemaValue(
|
raise InvalidSchemaValue(
|
||||||
"Value {0} is not less than {1}".format(
|
"Value {value} is not less than {type}", value, self.minimum)
|
||||||
value, self.minimum)
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.maximum is not None:
|
if self.maximum is not None:
|
||||||
if self.exclusive_maximum and value >= self.maximum:
|
if self.exclusive_maximum and value >= self.maximum:
|
||||||
raise InvalidSchemaValue(
|
raise InvalidSchemaValue(
|
||||||
"Value {0} is not greater than or equal to {1}".format(
|
"Value {value} is not greater than or equal to {type}", value, self.maximum)
|
||||||
value, self.maximum)
|
|
||||||
)
|
|
||||||
elif value > self.maximum:
|
elif value > self.maximum:
|
||||||
raise InvalidSchemaValue(
|
raise InvalidSchemaValue(
|
||||||
"Value {0} is not greater than {1}".format(
|
"Value {value} is not greater than {type}", value, self.maximum)
|
||||||
value, self.maximum)
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.multiple_of is not None and value % self.multiple_of:
|
if self.multiple_of is not None and value % self.multiple_of:
|
||||||
raise InvalidSchemaValue(
|
raise InvalidSchemaValue(
|
||||||
"Value {0} is not a multiple of {1}".format(
|
"Value {value} is not a multiple of {type}",
|
||||||
value, self.multiple_of)
|
value, self.multiple_of)
|
||||||
)
|
|
||||||
|
|
||||||
def _validate_string(self, value):
|
def _validate_string(self, value, custom_formatters=None):
|
||||||
try:
|
try:
|
||||||
schema_format = SchemaFormat(self.format)
|
schema_format = SchemaFormat(self.format)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# @todo: implement custom format validation support
|
msg = "Unsupported {0} format validation".format(self.format)
|
||||||
raise OpenAPISchemaError(
|
if custom_formatters is not None:
|
||||||
"Unsupported {0} format validation".format(self.format)
|
formatstring = custom_formatters.get(self.format)
|
||||||
)
|
if formatstring is None:
|
||||||
|
raise OpenAPISchemaError(msg)
|
||||||
|
else:
|
||||||
|
raise OpenAPISchemaError(msg)
|
||||||
else:
|
else:
|
||||||
format_validator_callable =\
|
formatstring =\
|
||||||
self.STRING_FORMAT_VALIDATOR_CALLABLE_GETTER[schema_format]
|
self.STRING_FORMAT_CALLABLE_GETTER[schema_format]
|
||||||
|
|
||||||
if not format_validator_callable(value):
|
if not formatstring.validate(value):
|
||||||
raise InvalidSchemaValue(
|
raise InvalidSchemaValue(
|
||||||
"Value of {0} not valid format of {1}".format(
|
"Value {value} not valid format {type}", value, self.format)
|
||||||
value, self.format)
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.min_length is not None:
|
if self.min_length is not None:
|
||||||
if self.min_length < 0:
|
if self.min_length < 0:
|
||||||
|
@ -427,8 +422,8 @@ class Schema(object):
|
||||||
)
|
)
|
||||||
if len(value) < self.min_length:
|
if len(value) < self.min_length:
|
||||||
raise InvalidSchemaValue(
|
raise InvalidSchemaValue(
|
||||||
"Value is shorter than the minimum length of {0}".format(
|
"Value is shorter ({value}) than the minimum length of {type}",
|
||||||
self.min_length)
|
len(value), self.min_length
|
||||||
)
|
)
|
||||||
if self.max_length is not None:
|
if self.max_length is not None:
|
||||||
if self.max_length < 0:
|
if self.max_length < 0:
|
||||||
|
@ -438,40 +433,40 @@ class Schema(object):
|
||||||
)
|
)
|
||||||
if len(value) > self.max_length:
|
if len(value) > self.max_length:
|
||||||
raise InvalidSchemaValue(
|
raise InvalidSchemaValue(
|
||||||
"Value is longer than the maximum length of {0}".format(
|
"Value is longer ({value}) than the maximum length of {type}",
|
||||||
self.max_length)
|
len(value), self.max_length
|
||||||
)
|
)
|
||||||
if self.pattern is not None and not self.pattern.search(value):
|
if self.pattern is not None and not self.pattern.search(value):
|
||||||
raise InvalidSchemaValue(
|
raise InvalidSchemaValue(
|
||||||
"Value {0} does not match the pattern {1}".format(
|
"Value {value} does not match the pattern {type}",
|
||||||
value, self.pattern.pattern)
|
value, self.pattern.pattern
|
||||||
)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _validate_object(self, value):
|
def _validate_object(self, value, custom_formatters=None):
|
||||||
properties = value.__dict__
|
properties = value.__dict__
|
||||||
|
|
||||||
if self.one_of:
|
if self.one_of:
|
||||||
valid_one_of_schema = None
|
valid_one_of_schema = None
|
||||||
for one_of_schema in self.one_of:
|
for one_of_schema in self.one_of:
|
||||||
try:
|
try:
|
||||||
self._validate_properties(properties, one_of_schema)
|
self._validate_properties(
|
||||||
|
properties, one_of_schema,
|
||||||
|
custom_formatters=custom_formatters)
|
||||||
except OpenAPISchemaError:
|
except OpenAPISchemaError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if valid_one_of_schema is not None:
|
if valid_one_of_schema is not None:
|
||||||
raise MultipleOneOfSchema(
|
raise MultipleOneOfSchema(self.type)
|
||||||
"Exactly one schema should be valid,"
|
|
||||||
"multiple found")
|
|
||||||
valid_one_of_schema = True
|
valid_one_of_schema = True
|
||||||
|
|
||||||
if valid_one_of_schema is None:
|
if valid_one_of_schema is None:
|
||||||
raise NoOneOfSchema(
|
raise NoOneOfSchema(self.type)
|
||||||
"Exactly one valid schema should be valid, None found.")
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self._validate_properties(properties)
|
self._validate_properties(properties,
|
||||||
|
custom_formatters=custom_formatters)
|
||||||
|
|
||||||
if self.min_properties is not None:
|
if self.min_properties is not None:
|
||||||
if self.min_properties < 0:
|
if self.min_properties < 0:
|
||||||
|
@ -482,9 +477,8 @@ class Schema(object):
|
||||||
|
|
||||||
if len(properties) < self.min_properties:
|
if len(properties) < self.min_properties:
|
||||||
raise InvalidSchemaValue(
|
raise InvalidSchemaValue(
|
||||||
"Value must contain at least {0} properties,"
|
"Value must contain at least {type} properties,"
|
||||||
" {1} found".format(
|
" {value} found", len(properties), self.min_properties
|
||||||
self.min_properties, len(properties))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.max_properties is not None:
|
if self.max_properties is not None:
|
||||||
|
@ -495,14 +489,14 @@ class Schema(object):
|
||||||
)
|
)
|
||||||
if len(properties) > self.max_properties:
|
if len(properties) > self.max_properties:
|
||||||
raise InvalidSchemaValue(
|
raise InvalidSchemaValue(
|
||||||
"Value must contain at most {0} properties,"
|
"Value must contain at most {type} properties,"
|
||||||
" {1} found".format(
|
" {value} found", len(properties), self.max_properties
|
||||||
self.max_properties, len(properties))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _validate_properties(self, value, one_of_schema=None):
|
def _validate_properties(self, value, one_of_schema=None,
|
||||||
|
custom_formatters=None):
|
||||||
all_props = self.get_all_properties()
|
all_props = self.get_all_properties()
|
||||||
all_props_names = self.get_all_properties_names()
|
all_props_names = self.get_all_properties_names()
|
||||||
all_req_props_names = self.get_all_required_properties_names()
|
all_req_props_names = self.get_all_required_properties_names()
|
||||||
|
@ -517,24 +511,25 @@ class Schema(object):
|
||||||
value_props_names = value.keys()
|
value_props_names = value.keys()
|
||||||
extra_props = set(value_props_names) - set(all_props_names)
|
extra_props = set(value_props_names) - set(all_props_names)
|
||||||
if extra_props and self.additional_properties is None:
|
if extra_props and self.additional_properties is None:
|
||||||
raise UndefinedSchemaProperty(
|
raise UndefinedSchemaProperty(extra_props)
|
||||||
"Undefined properties in schema: {0}".format(extra_props))
|
|
||||||
|
|
||||||
for prop_name in extra_props:
|
for prop_name in extra_props:
|
||||||
prop_value = value[prop_name]
|
prop_value = value[prop_name]
|
||||||
self.additional_properties.validate(
|
self.additional_properties.validate(
|
||||||
prop_value)
|
prop_value, custom_formatters=custom_formatters)
|
||||||
|
|
||||||
for prop_name, prop in iteritems(all_props):
|
for prop_name, prop in iteritems(all_props):
|
||||||
try:
|
try:
|
||||||
prop_value = value[prop_name]
|
prop_value = value[prop_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if prop_name in all_req_props_names:
|
if prop_name in all_req_props_names:
|
||||||
raise MissingSchemaProperty(
|
raise MissingSchemaProperty(prop_name)
|
||||||
"Missing schema property {0}".format(prop_name))
|
|
||||||
if not prop.nullable and not prop.default:
|
if not prop.nullable and not prop.default:
|
||||||
continue
|
continue
|
||||||
prop_value = prop.default
|
prop_value = prop.default
|
||||||
prop.validate(prop_value)
|
try:
|
||||||
|
prop.validate(prop_value, custom_formatters=custom_formatters)
|
||||||
|
except OpenAPISchemaError as exc:
|
||||||
|
raise InvalidSchemaProperty(prop_name, original_exception=exc)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
from openapi_core.schema.exceptions import OpenAPIMappingError
|
from openapi_core.schema.exceptions import OpenAPIMappingError
|
||||||
|
|
||||||
|
import attr
|
||||||
|
|
||||||
|
|
||||||
class OpenAPIServerError(OpenAPIMappingError):
|
class OpenAPIServerError(OpenAPIMappingError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
class InvalidServer(OpenAPIServerError):
|
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:
|
if spec_server.default_url in full_url_pattern:
|
||||||
return spec_server
|
return spec_server
|
||||||
|
|
||||||
raise InvalidServer(
|
raise InvalidServer(full_url_pattern)
|
||||||
"Invalid request server {0}".format(full_url_pattern))
|
|
||||||
|
|
||||||
def get_server_url(self, index=0):
|
def get_server_url(self, index=0):
|
||||||
return self.servers[index].default_url
|
return self.servers[index].default_url
|
||||||
|
@ -41,9 +40,7 @@ class Spec(object):
|
||||||
try:
|
try:
|
||||||
return self.paths[path_pattern].operations[http_method]
|
return self.paths[path_pattern].operations[http_method]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise InvalidOperation(
|
raise InvalidOperation(path_pattern, http_method)
|
||||||
"Unknown operation path {0} with method {1}".format(
|
|
||||||
path_pattern, http_method))
|
|
||||||
|
|
||||||
def get_schema(self, name):
|
def get_schema(self, name):
|
||||||
return self.components.schemas[name]
|
return self.components.schemas[name]
|
||||||
|
|
|
@ -11,8 +11,9 @@ from openapi_core.validation.util import get_operation_pattern
|
||||||
|
|
||||||
class RequestValidator(object):
|
class RequestValidator(object):
|
||||||
|
|
||||||
def __init__(self, spec):
|
def __init__(self, spec, custom_formatters=None):
|
||||||
self.spec = spec
|
self.spec = spec
|
||||||
|
self.custom_formatters = custom_formatters
|
||||||
|
|
||||||
def validate(self, request):
|
def validate(self, request):
|
||||||
try:
|
try:
|
||||||
|
@ -52,7 +53,7 @@ class RequestValidator(object):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
value = param.unmarshal(raw_value)
|
value = param.unmarshal(raw_value, self.custom_formatters)
|
||||||
except OpenAPIMappingError as exc:
|
except OpenAPIMappingError as exc:
|
||||||
errors.append(exc)
|
errors.append(exc)
|
||||||
else:
|
else:
|
||||||
|
@ -78,7 +79,7 @@ class RequestValidator(object):
|
||||||
errors.append(exc)
|
errors.append(exc)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
body = media_type.unmarshal(raw_body)
|
body = media_type.unmarshal(raw_body, self.custom_formatters)
|
||||||
except OpenAPIMappingError as exc:
|
except OpenAPIMappingError as exc:
|
||||||
errors.append(exc)
|
errors.append(exc)
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,9 @@ from openapi_core.validation.util import get_operation_pattern
|
||||||
|
|
||||||
class ResponseValidator(object):
|
class ResponseValidator(object):
|
||||||
|
|
||||||
def __init__(self, spec):
|
def __init__(self, spec, custom_formatters=None):
|
||||||
self.spec = spec
|
self.spec = spec
|
||||||
|
self.custom_formatters = custom_formatters
|
||||||
|
|
||||||
def validate(self, request, response):
|
def validate(self, request, response):
|
||||||
try:
|
try:
|
||||||
|
@ -60,7 +61,7 @@ class ResponseValidator(object):
|
||||||
errors.append(exc)
|
errors.append(exc)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
data = media_type.unmarshal(raw_data)
|
data = media_type.unmarshal(raw_data, self.custom_formatters)
|
||||||
except OpenAPIMappingError as exc:
|
except OpenAPIMappingError as exc:
|
||||||
errors.append(exc)
|
errors.append(exc)
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
openapi-spec-validator
|
openapi-spec-validator
|
||||||
six
|
six
|
||||||
lazy-object-proxy
|
lazy-object-proxy
|
||||||
|
attrs
|
||||||
|
|
|
@ -4,3 +4,4 @@ lazy-object-proxy
|
||||||
backports.functools-lru-cache
|
backports.functools-lru-cache
|
||||||
backports.functools-partialmethod
|
backports.functools-partialmethod
|
||||||
enum34
|
enum34
|
||||||
|
attrs
|
||||||
|
|
1
setup.py
1
setup.py
|
@ -44,6 +44,7 @@ class PyTest(TestCommand):
|
||||||
'--cov', 'openapi_core',
|
'--cov', 'openapi_core',
|
||||||
'--cov-report', 'term-missing',
|
'--cov-report', 'term-missing',
|
||||||
'--cov-report', 'xml:reports/coverage.xml',
|
'--cov-report', 'xml:reports/coverage.xml',
|
||||||
|
'tests',
|
||||||
]
|
]
|
||||||
self.test_suite = True
|
self.test_suite = True
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ from openapi_core.schema.paths.models import Path
|
||||||
from openapi_core.schema.request_bodies.models import RequestBody
|
from openapi_core.schema.request_bodies.models import RequestBody
|
||||||
from openapi_core.schema.responses.models import Response
|
from openapi_core.schema.responses.models import Response
|
||||||
from openapi_core.schema.schemas.exceptions import (
|
from openapi_core.schema.schemas.exceptions import (
|
||||||
UndefinedSchemaProperty, MissingSchemaProperty, NoOneOfSchema,
|
|
||||||
NoValidSchema,
|
NoValidSchema,
|
||||||
)
|
)
|
||||||
from openapi_core.schema.schemas.models import Schema
|
from openapi_core.schema.schemas.models import Schema
|
||||||
|
@ -615,7 +614,7 @@ class TestPetstore(object):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
with pytest.raises(NoOneOfSchema):
|
with pytest.raises(InvalidMediaTypeValue):
|
||||||
request.get_body(spec)
|
request.get_body(spec)
|
||||||
|
|
||||||
def test_post_cats_only_required_body(self, spec, spec_dict):
|
def test_post_cats_only_required_body(self, spec, spec_dict):
|
||||||
|
@ -952,7 +951,7 @@ class TestPetstore(object):
|
||||||
|
|
||||||
assert parameters == {}
|
assert parameters == {}
|
||||||
|
|
||||||
with pytest.raises(UndefinedSchemaProperty):
|
with pytest.raises(InvalidMediaTypeValue):
|
||||||
request.get_body(spec)
|
request.get_body(spec)
|
||||||
|
|
||||||
def test_post_tags_empty_body(self, spec, spec_dict):
|
def test_post_tags_empty_body(self, spec, spec_dict):
|
||||||
|
@ -970,7 +969,7 @@ class TestPetstore(object):
|
||||||
|
|
||||||
assert parameters == {}
|
assert parameters == {}
|
||||||
|
|
||||||
with pytest.raises(MissingSchemaProperty):
|
with pytest.raises(InvalidMediaTypeValue):
|
||||||
request.get_body(spec)
|
request.get_body(spec)
|
||||||
|
|
||||||
def test_post_tags_wrong_property_type(self, spec):
|
def test_post_tags_wrong_property_type(self, spec):
|
||||||
|
|
Loading…
Reference in a new issue