mirror of
https://github.com/correl/openapi-core.git
synced 2024-11-22 03:00:10 +00:00
Primitive types unmarshallers
This commit is contained in:
parent
9376b2e2da
commit
9d9629b4bd
5 changed files with 160 additions and 67 deletions
|
@ -47,6 +47,7 @@ class MediaType(object):
|
|||
raise InvalidMediaTypeValue(exc)
|
||||
|
||||
try:
|
||||
return self.schema.validate(unmarshalled, custom_formatters=custom_formatters)
|
||||
return self.schema.validate(
|
||||
unmarshalled, custom_formatters=custom_formatters)
|
||||
except OpenAPISchemaError as exc:
|
||||
raise InvalidMediaTypeValue(exc)
|
||||
|
|
|
@ -118,6 +118,7 @@ class Parameter(object):
|
|||
raise InvalidParameterValue(self.name, exc)
|
||||
|
||||
try:
|
||||
return self.schema.validate(unmarshalled, custom_formatters=custom_formatters)
|
||||
return self.schema.validate(
|
||||
unmarshalled, custom_formatters=custom_formatters)
|
||||
except OpenAPISchemaError as exc:
|
||||
raise InvalidParameterValue(self.name, exc)
|
||||
|
|
|
@ -77,3 +77,17 @@ class MultipleOneOfSchema(OpenAPISchemaError):
|
|||
|
||||
def __str__(self):
|
||||
return "Exactly one schema type {0} should be valid, more than one found".format(self.type)
|
||||
|
||||
|
||||
class UnmarshallerError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@attr.s
|
||||
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)
|
||||
|
|
|
@ -16,6 +16,7 @@ from openapi_core.schema.schemas.exceptions import (
|
|||
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
|
||||
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema, NoValidSchema,
|
||||
UndefinedItemsSchema, InvalidCustomFormatSchemaValue, InvalidSchemaProperty,
|
||||
UnmarshallerStrictTypeError,
|
||||
)
|
||||
from openapi_core.schema.schemas.util import (
|
||||
forcebool, format_date, format_datetime, format_byte, format_uuid,
|
||||
|
@ -155,14 +156,19 @@ class Schema(object):
|
|||
return set(required)
|
||||
|
||||
def get_cast_mapping(self, custom_formatters=None, strict=True):
|
||||
primitive_unmarshallers = self.get_primitive_unmarshallers(
|
||||
custom_formatters=custom_formatters)
|
||||
|
||||
primitive_unmarshallers_partial = dict(
|
||||
(t, functools.partial(u, type_format=self.format, strict=strict))
|
||||
for t, u in primitive_unmarshallers.items()
|
||||
)
|
||||
|
||||
pass_defaults = lambda f: functools.partial(
|
||||
f, custom_formatters=custom_formatters, strict=strict)
|
||||
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy()
|
||||
mapping.update(primitive_unmarshallers_partial)
|
||||
mapping.update({
|
||||
SchemaType.STRING: pass_defaults(self._unmarshal_string),
|
||||
SchemaType.BOOLEAN: pass_defaults(self._unmarshal_boolean),
|
||||
SchemaType.INTEGER: pass_defaults(self._unmarshal_integer),
|
||||
SchemaType.NUMBER: pass_defaults(self._unmarshal_number),
|
||||
SchemaType.ANY: pass_defaults(self._unmarshal_any),
|
||||
SchemaType.ARRAY: pass_defaults(self._unmarshal_collection),
|
||||
SchemaType.OBJECT: pass_defaults(self._unmarshal_object),
|
||||
|
@ -184,6 +190,10 @@ class Schema(object):
|
|||
raise InvalidSchemaValue("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)
|
||||
|
||||
cast_mapping = self.get_cast_mapping(
|
||||
custom_formatters=custom_formatters, strict=strict)
|
||||
|
||||
|
@ -193,6 +203,9 @@ class Schema(object):
|
|||
cast_callable = cast_mapping[self.type]
|
||||
try:
|
||||
return cast_callable(value)
|
||||
except UnmarshallerStrictTypeError:
|
||||
raise InvalidSchemaValue(
|
||||
"Value {value} is not of type {type}", value, self.type)
|
||||
except ValueError:
|
||||
raise InvalidSchemaValue(
|
||||
"Failed to cast value {value} to type {type}", value, self.type)
|
||||
|
@ -207,72 +220,27 @@ class Schema(object):
|
|||
if casted is None and not self.required:
|
||||
return None
|
||||
|
||||
if self.enum and casted not in self.enum:
|
||||
raise InvalidSchemaValue(
|
||||
"Value {value} not in enum choices: {type}", value, self.enum)
|
||||
|
||||
return casted
|
||||
|
||||
def _unmarshal_string(self, value, custom_formatters=None, strict=True):
|
||||
if strict and not isinstance(value, (text_type, binary_type)):
|
||||
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
|
||||
def get_primitive_unmarshallers(self, **options):
|
||||
from openapi_core.schema.schemas.unmarshallers import (
|
||||
StringUnmarshaller, BooleanUnmarshaller, IntegerUnmarshaller,
|
||||
NumberUnmarshaller,
|
||||
)
|
||||
|
||||
try:
|
||||
schema_format = SchemaFormat(self.format)
|
||||
except ValueError:
|
||||
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 InvalidSchemaValue(msg, value, self.format)
|
||||
else:
|
||||
raise InvalidSchemaValue(msg, value, self.format)
|
||||
else:
|
||||
if self.enum and value not in self.enum:
|
||||
raise InvalidSchemaValue(
|
||||
"Value {value} not in enum choices: {type}", value, self.enum)
|
||||
formatstring = self.STRING_FORMAT_CALLABLE_GETTER[schema_format]
|
||||
unmarshallers_classes = {
|
||||
SchemaType.STRING: StringUnmarshaller,
|
||||
SchemaType.BOOLEAN: BooleanUnmarshaller,
|
||||
SchemaType.INTEGER: IntegerUnmarshaller,
|
||||
SchemaType.NUMBER: NumberUnmarshaller,
|
||||
}
|
||||
|
||||
try:
|
||||
return formatstring.unmarshal(value)
|
||||
except ValueError as exc:
|
||||
raise InvalidCustomFormatSchemaValue(
|
||||
"Failed to format value {value} to format {type}: {exception}", value, self.format, exc)
|
||||
unmarshallers = dict(
|
||||
(t, klass(**options))
|
||||
for t, klass in unmarshallers_classes.items()
|
||||
)
|
||||
|
||||
def _unmarshal_integer(self, value, custom_formatters=None, strict=True):
|
||||
if strict and not isinstance(value, integer_types):
|
||||
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
|
||||
|
||||
return int(value)
|
||||
|
||||
def _unmarshal_number(self, value, custom_formatters=None, strict=True):
|
||||
if strict and not isinstance(value, (float, ) + integer_types):
|
||||
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
|
||||
|
||||
try:
|
||||
schema_format = SchemaFormat(self.format)
|
||||
except ValueError:
|
||||
msg = "Unsupported format {type} unmarshalling for value {value}"
|
||||
if custom_formatters is not None:
|
||||
formatnumber = custom_formatters.get(self.format)
|
||||
if formatnumber is None:
|
||||
raise InvalidSchemaValue(msg, value, self.format)
|
||||
else:
|
||||
raise InvalidSchemaValue(msg, value, self.format)
|
||||
else:
|
||||
formatnumber = self.NUMBER_FORMAT_CALLABLE_GETTER[schema_format]
|
||||
|
||||
try:
|
||||
return formatnumber.unmarshal(value)
|
||||
except ValueError as exc:
|
||||
raise InvalidCustomFormatSchemaValue(
|
||||
"Failed to format value {value} to format {type}: {exception}", value, self.format, exc)
|
||||
|
||||
def _unmarshal_boolean(self, value, custom_formatters=None, strict=True):
|
||||
if strict and not isinstance(value, (bool, )):
|
||||
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
|
||||
|
||||
return forcebool(value)
|
||||
return unmarshallers
|
||||
|
||||
def _unmarshal_any(self, value, custom_formatters=None, strict=True):
|
||||
types_resolve_order = [
|
||||
|
@ -301,6 +269,8 @@ class Schema(object):
|
|||
cast_callable = cast_mapping[schema_type]
|
||||
try:
|
||||
return cast_callable(value)
|
||||
except UnmarshallerStrictTypeError:
|
||||
continue
|
||||
# @todo: remove ValueError when validation separated
|
||||
except (OpenAPISchemaError, TypeError, ValueError):
|
||||
continue
|
||||
|
|
107
openapi_core/schema/schemas/unmarshallers.py
Normal file
107
openapi_core/schema/schemas/unmarshallers.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
from six import text_type, binary_type, integer_types
|
||||
|
||||
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
|
||||
from openapi_core.schema.schemas.exceptions import (
|
||||
InvalidSchemaValue, InvalidCustomFormatSchemaValue,
|
||||
OpenAPISchemaError, MultipleOneOfSchema, NoOneOfSchema,
|
||||
InvalidSchemaProperty,
|
||||
UnmarshallerStrictTypeError,
|
||||
)
|
||||
from openapi_core.schema.schemas.util import (
|
||||
forcebool, format_date, format_datetime, format_byte, format_uuid,
|
||||
format_number,
|
||||
)
|
||||
|
||||
|
||||
class StrictUnmarshaller(object):
|
||||
|
||||
STRICT_TYPES = ()
|
||||
|
||||
def __call__(self, value, type_format=SchemaFormat.NONE, strict=True):
|
||||
if self.STRICT_TYPES and strict and not isinstance(
|
||||
value, self.STRICT_TYPES):
|
||||
raise UnmarshallerStrictTypeError(value, self.STRICT_TYPES)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class PrimitiveTypeUnmarshaller(StrictUnmarshaller):
|
||||
|
||||
FORMATTERS = {
|
||||
SchemaFormat.NONE: lambda x: x,
|
||||
}
|
||||
|
||||
def __init__(self, custom_formatters=None):
|
||||
if custom_formatters is None:
|
||||
custom_formatters = {}
|
||||
self.custom_formatters = custom_formatters
|
||||
|
||||
def __call__(self, value, type_format=SchemaFormat.NONE, strict=True):
|
||||
value = super(PrimitiveTypeUnmarshaller, self).__call__(
|
||||
value, type_format=type_format, strict=strict)
|
||||
|
||||
try:
|
||||
schema_format = SchemaFormat(type_format)
|
||||
except ValueError:
|
||||
formatter = self.custom_formatters.get(type_format)
|
||||
else:
|
||||
formatters = self.get_formatters()
|
||||
formatter = formatters.get(schema_format)
|
||||
|
||||
if formatter is None:
|
||||
raise InvalidSchemaValue(
|
||||
"Unsupported format {type} unmarshalling "
|
||||
"for value {value}",
|
||||
value, type_format)
|
||||
|
||||
try:
|
||||
return formatter(value)
|
||||
except ValueError as exc:
|
||||
raise InvalidCustomFormatSchemaValue(
|
||||
"Failed to format value {value} to format {type}: {exception}",
|
||||
value, type_format, exc)
|
||||
|
||||
def get_formatters(self):
|
||||
return self.FORMATTERS
|
||||
|
||||
|
||||
class StringUnmarshaller(PrimitiveTypeUnmarshaller):
|
||||
|
||||
STRICT_TYPES = (text_type, binary_type)
|
||||
FORMATTERS = {
|
||||
SchemaFormat.NONE: text_type,
|
||||
SchemaFormat.PASSWORD: text_type,
|
||||
SchemaFormat.DATE: format_date,
|
||||
SchemaFormat.DATETIME: format_datetime,
|
||||
SchemaFormat.BINARY: binary_type,
|
||||
SchemaFormat.UUID: format_uuid,
|
||||
SchemaFormat.BYTE: format_byte,
|
||||
}
|
||||
|
||||
|
||||
class IntegerUnmarshaller(PrimitiveTypeUnmarshaller):
|
||||
|
||||
STRICT_TYPES = integer_types
|
||||
FORMATTERS = {
|
||||
SchemaFormat.NONE: int,
|
||||
SchemaFormat.INT32: int,
|
||||
SchemaFormat.INT64: int,
|
||||
}
|
||||
|
||||
|
||||
class NumberUnmarshaller(PrimitiveTypeUnmarshaller):
|
||||
|
||||
STRICT_TYPES = (float, ) + integer_types
|
||||
FORMATTERS = {
|
||||
SchemaFormat.NONE: format_number,
|
||||
SchemaFormat.FLOAT: float,
|
||||
SchemaFormat.DOUBLE: float,
|
||||
}
|
||||
|
||||
|
||||
class BooleanUnmarshaller(PrimitiveTypeUnmarshaller):
|
||||
|
||||
STRICT_TYPES = (bool, )
|
||||
FORMATTERS = {
|
||||
SchemaFormat.NONE: forcebool,
|
||||
}
|
Loading…
Reference in a new issue