Separate cast and unmarshal

This commit is contained in:
p1c2u 2019-09-02 23:14:37 +01:00
parent 0e30b71a77
commit 5273a7ff7c
2 changed files with 62 additions and 31 deletions

View file

@ -108,11 +108,16 @@ class Parameter(object):
except (ValueError, AttributeError) as exc: except (ValueError, AttributeError) as exc:
raise InvalidParameterValue(self.name, exc) raise InvalidParameterValue(self.name, exc)
try:
casted = self.schema.cast(deserialized)
except OpenAPISchemaError as exc:
raise InvalidParameterValue(self.name, exc)
try: try:
unmarshalled = self.schema.unmarshal( unmarshalled = self.schema.unmarshal(
deserialized, casted,
custom_formatters=custom_formatters, custom_formatters=custom_formatters,
strict=False, strict=True,
) )
except OpenAPISchemaError as exc: except OpenAPISchemaError as exc:
raise InvalidParameterValue(self.name, exc) raise InvalidParameterValue(self.name, exc)

View file

@ -38,7 +38,13 @@ class Format(object):
class Schema(object): class Schema(object):
"""Represents an OpenAPI Schema.""" """Represents an OpenAPI Schema."""
DEFAULT_CAST_CALLABLE_GETTER = { TYPE_CAST_CALLABLE_GETTER = {
SchemaType.INTEGER: int,
SchemaType.NUMBER: float,
SchemaType.BOOLEAN: forcebool,
}
DEFAULT_UNMARSHAL_CALLABLE_GETTER = {
} }
STRING_FORMAT_CALLABLE_GETTER = { STRING_FORMAT_CALLABLE_GETTER = {
@ -155,7 +161,39 @@ class Schema(object):
return set(required) return set(required)
def get_cast_mapping(self, custom_formatters=None, strict=True): def are_additional_properties_allowed(self, one_of_schema=None):
return (
(self.additional_properties is not False) and
(one_of_schema is None or
one_of_schema.additional_properties is not False)
)
def get_cast_mapping(self):
mapping = self.TYPE_CAST_CALLABLE_GETTER.copy()
mapping.update({
SchemaType.ARRAY: self._cast_collection,
})
return defaultdict(lambda: lambda x: x, mapping)
def cast(self, value):
"""Cast value from string to schema type"""
if value is None:
return value
cast_mapping = self.get_cast_mapping()
cast_callable = cast_mapping[self.type]
try:
return cast_callable(value)
except ValueError:
raise InvalidSchemaValue(
"Failed to cast value {value} to type {type}", value, self.type)
def _cast_collection(self, value):
return list(map(self.items.cast, value))
def get_unmarshal_mapping(self, custom_formatters=None, strict=True):
primitive_unmarshallers = self.get_primitive_unmarshallers( primitive_unmarshallers = self.get_primitive_unmarshallers(
custom_formatters=custom_formatters) custom_formatters=custom_formatters)
@ -166,7 +204,7 @@ class Schema(object):
pass_defaults = lambda f: functools.partial( pass_defaults = lambda f: functools.partial(
f, custom_formatters=custom_formatters, strict=strict) f, custom_formatters=custom_formatters, strict=strict)
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy() mapping = self.DEFAULT_UNMARSHAL_CALLABLE_GETTER.copy()
mapping.update(primitive_unmarshallers_partial) mapping.update(primitive_unmarshallers_partial)
mapping.update({ mapping.update({
SchemaType.ANY: pass_defaults(self._unmarshal_any), SchemaType.ANY: pass_defaults(self._unmarshal_any),
@ -176,15 +214,10 @@ class Schema(object):
return defaultdict(lambda: lambda x: x, mapping) return defaultdict(lambda: lambda x: x, mapping)
def are_additional_properties_allowed(self, one_of_schema=None): def unmarshal(self, value, custom_formatters=None, strict=True):
return ( """Unmarshal parameter from the value."""
(self.additional_properties is not False) and if self.deprecated:
(one_of_schema is None or warnings.warn("The schema is deprecated", DeprecationWarning)
one_of_schema.additional_properties is not False)
)
def cast(self, value, custom_formatters=None, strict=True):
"""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", value, self.type) raise InvalidSchemaValue("Null value for non-nullable schema", value, self.type)
@ -194,15 +227,15 @@ class Schema(object):
raise InvalidSchemaValue( raise InvalidSchemaValue(
"Value {value} not in enum choices: {type}", value, self.enum) "Value {value} not in enum choices: {type}", value, self.enum)
cast_mapping = self.get_cast_mapping( unmarshal_mapping = self.get_unmarshal_mapping(
custom_formatters=custom_formatters, strict=strict) custom_formatters=custom_formatters, strict=strict)
if self.type is not SchemaType.STRING and value == '': if self.type is not SchemaType.STRING and value == '':
return None return None
cast_callable = cast_mapping[self.type] unmarshal_callable = unmarshal_mapping[self.type]
try: try:
return cast_callable(value) unmarshalled = unmarshal_callable(value)
except UnmarshallerStrictTypeError: except UnmarshallerStrictTypeError:
raise InvalidSchemaValue( raise InvalidSchemaValue(
"Value {value} is not of type {type}", value, self.type) "Value {value} is not of type {type}", value, self.type)
@ -210,17 +243,10 @@ class Schema(object):
raise InvalidSchemaValue( raise InvalidSchemaValue(
"Failed to cast value {value} to type {type}", value, self.type) "Failed to cast value {value} to type {type}", value, self.type)
def unmarshal(self, value, custom_formatters=None, strict=True): if unmarshalled is None and not self.required:
"""Unmarshal parameter from the value."""
if self.deprecated:
warnings.warn("The schema is deprecated", DeprecationWarning)
casted = self.cast(value, custom_formatters=custom_formatters, strict=strict)
if casted is None and not self.required:
return None return None
return casted return unmarshalled
def get_primitive_unmarshallers(self, **options): def get_primitive_unmarshallers(self, **options):
from openapi_core.schema.schemas.unmarshallers import ( from openapi_core.schema.schemas.unmarshallers import (
@ -247,18 +273,18 @@ class Schema(object):
SchemaType.OBJECT, SchemaType.ARRAY, SchemaType.BOOLEAN, SchemaType.OBJECT, SchemaType.ARRAY, SchemaType.BOOLEAN,
SchemaType.INTEGER, SchemaType.NUMBER, SchemaType.STRING, SchemaType.INTEGER, SchemaType.NUMBER, SchemaType.STRING,
] ]
cast_mapping = self.get_cast_mapping() unmarshal_mapping = self.get_unmarshal_mapping()
if self.one_of: if self.one_of:
result = None result = None
for subschema in self.one_of: for subschema in self.one_of:
try: try:
casted = subschema.cast(value, custom_formatters) unmarshalled = subschema.unmarshal(value, custom_formatters)
except (OpenAPISchemaError, TypeError, ValueError): except (OpenAPISchemaError, TypeError, ValueError):
continue continue
else: else:
if result is not None: if result is not None:
raise MultipleOneOfSchema(self.type) raise MultipleOneOfSchema(self.type)
result = casted result = unmarshalled
if result is None: if result is None:
raise NoOneOfSchema(self.type) raise NoOneOfSchema(self.type)
@ -266,9 +292,9 @@ class Schema(object):
return result return result
else: else:
for schema_type in types_resolve_order: for schema_type in types_resolve_order:
cast_callable = cast_mapping[schema_type] unmarshal_callable = unmarshal_mapping[schema_type]
try: try:
return cast_callable(value) return unmarshal_callable(value)
except UnmarshallerStrictTypeError: except UnmarshallerStrictTypeError:
continue continue
# @todo: remove ValueError when validation separated # @todo: remove ValueError when validation separated