diff --git a/openapi_core/schema/schemas/exceptions.py b/openapi_core/schema/schemas/exceptions.py index c984578c..5ff64e8f 100644 --- a/openapi_core/schema/schemas/exceptions.py +++ b/openapi_core/schema/schemas/exceptions.py @@ -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) diff --git a/openapi_core/schema/schemas/models.py b/openapi_core/schema/schemas/models.py index ffe52ab2..0b484d97 100644 --- a/openapi_core/schema/schemas/models.py +++ b/openapi_core/schema/schemas/models.py @@ -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: @@ -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) diff --git a/tests/unit/schema/test_schemas.py b/tests/unit/schema/test_schemas.py index 0b747aac..ad3b5468 100644 --- a/tests/unit/schema/test_schemas.py +++ b/tests/unit/schema/test_schemas.py @@ -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 @@ -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) @@ -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')