Skip to content

Apply custom_formatters on ANY-typed Schemas #150

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion openapi_core/schema/schemas/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,4 @@ class UnmarshallerStrictTypeError(UnmarshallerError):

def __str__(self):
return "Value {value} is not one of types {types}".format(
self.value, self.types)
value=self.value, types=self.types)
9 changes: 5 additions & 4 deletions openapi_core/schema/schemas/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ def _unmarshal_any(self, value, custom_formatters=None, strict=True):
SchemaType.OBJECT, SchemaType.ARRAY, SchemaType.BOOLEAN,
SchemaType.INTEGER, SchemaType.NUMBER, SchemaType.STRING,
]
cast_mapping = self.get_cast_mapping()
cast_mapping = self.get_cast_mapping(custom_formatters=custom_formatters, strict=strict)
if self.one_of:
result = None
for subschema in self.one_of:
Expand All @@ -269,10 +269,11 @@ def _unmarshal_any(self, value, custom_formatters=None, strict=True):
cast_callable = cast_mapping[schema_type]
try:
return cast_callable(value)
except UnmarshallerStrictTypeError:
continue
except UnmarshallerStrictTypeError as ex:
log.debug("Failed to unmarshal '{}' as {}: ".format(value, schema_type, str(ex)))
# @todo: remove ValueError when validation separated
except (OpenAPISchemaError, TypeError, ValueError):
except (OpenAPISchemaError, TypeError, ValueError) as ex:
log.debug("Failed to unmarshal '{}' as {}: ".format(value, schema_type, str(ex)))
continue

raise NoValidSchema(value)
Expand Down
83 changes: 67 additions & 16 deletions tests/unit/schema/test_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
from openapi_core.schema.schemas.exceptions import (
InvalidSchemaValue, MultipleOneOfSchema, NoOneOfSchema, OpenAPISchemaError,
UndefinedSchemaProperty
)
UndefinedSchemaProperty,
InvalidCustomFormatSchemaValue)
from openapi_core.schema.schemas.models import Schema

from six import b, u
Expand Down Expand Up @@ -121,20 +121,75 @@ def test_string_format_datetime(self):

assert result == datetime.datetime(2018, 1, 2, 0, 0)

@pytest.mark.xfail(reason="No custom formats support atm")
def test_string_format_custom(self):
custom_format = 'custom'
schema = Schema('string', schema_format=custom_format)
value = 'x'

with mock.patch.dict(
Schema.STRING_FORMAT_CAST_CALLABLE_GETTER,
{custom_format: lambda x: x + '-custom'},
):
result = schema.unmarshal(value)
custom_formatters = {custom_format: lambda x: x + '-custom'}
result = schema.unmarshal(value, custom_formatters=custom_formatters)

assert result == 'x-custom'

def test_custom_format_inside_objects(self):
custom_format = 'custom'
prop_schema = Schema('string', schema_format=custom_format)

def properties():
yield ('name', prop_schema)

# TODO: Fix architecture for custom_formatters.
''' custom_formatters for primitive types are simply callables which
return that same primitive,
When applied to properties, they also have to have a validate, so
they are no longer simply callables
'''
class custom_string_formatter(object):
def __call__(self, value):
return "%s-custom" % value

def validate(self, value):
return value.endswith('-custom')

obj_schema = Schema('object', properties=properties())
value = {"name": "Joseph"}

custom_formatters = {custom_format: custom_string_formatter()}
result = obj_schema.unmarshal(value,
custom_formatters=custom_formatters)

assert result.name == 'Joseph-custom'

def test_custom_format_inside_any(self):
custom_format = 'custom'
prop_schema = Schema('string', schema_format=custom_format)

def properties():
yield ('name', prop_schema)

# TODO: Fix architecture for custom_formatters.
''' custom_formatters for primitive types are simply callables which
return that same primitive,
When applied to properties, they also have to have a validate, so
they are no longer simply callables
'''
class custom_string_formatter(object):
def __call__(self, value):
return "%s-custom" % value

def validate(self, value):
return value.endswith('-custom')

# Force SchemaType.ANY
obj_schema = Schema(None, properties=properties())
value = {"name": "Joseph"}

custom_formatters = {custom_format: custom_string_formatter()}
result = obj_schema.unmarshal(value,
custom_formatters=custom_formatters)

assert result.name == 'Joseph-custom'

def test_string_format_unknown(self):
unknown_format = 'unknown'
schema = Schema('string', schema_format=unknown_format)
Expand All @@ -143,19 +198,15 @@ def test_string_format_unknown(self):
with pytest.raises(OpenAPISchemaError):
schema.unmarshal(value)

@pytest.mark.xfail(reason="No custom formats support atm")
def test_string_format_invalid_value(self):
custom_format = 'custom'
schema = Schema('string', schema_format=custom_format)
value = 'x'

with mock.patch.dict(
Schema.STRING_FORMAT_CALLABLE_GETTER,
{custom_format: mock.Mock(side_effect=ValueError())},
), pytest.raises(
InvalidSchemaValue, message='Failed to format value'
):
schema.unmarshal(value)
formatters = {custom_format: mock.Mock(side_effect=ValueError())}
with pytest.raises(InvalidCustomFormatSchemaValue) as ex:
schema.unmarshal(value, custom_formatters=formatters)
assert isinstance(ex.original_exception, ValueError)

def test_integer_valid(self):
schema = Schema('integer')
Expand Down