diff --git a/openapi_core/unmarshalling/schemas/exceptions.py b/openapi_core/unmarshalling/schemas/exceptions.py index defd2142..433de337 100644 --- a/openapi_core/unmarshalling/schemas/exceptions.py +++ b/openapi_core/unmarshalling/schemas/exceptions.py @@ -19,21 +19,3 @@ class FormatterNotFoundError(UnmarshallerError): def __str__(self) -> str: return f"Formatter not found for {self.type_format} format" - - -@dataclass -class FormatUnmarshalError(UnmarshallerError): - """Unable to unmarshal value for format""" - - value: str - type: str - original_exception: Exception - - def __str__(self) -> str: - return ( - "Unable to unmarshal value {value} for format {type}: {exception}" - ).format( - value=self.value, - type=self.type, - exception=self.original_exception, - ) diff --git a/openapi_core/unmarshalling/schemas/unmarshallers.py b/openapi_core/unmarshalling/schemas/unmarshallers.py index 575d24a8..53ddcc34 100644 --- a/openapi_core/unmarshalling/schemas/unmarshallers.py +++ b/openapi_core/unmarshalling/schemas/unmarshallers.py @@ -15,8 +15,6 @@ from openapi_core.unmarshalling.schemas.datatypes import ( FormatUnmarshallersDict, ) -from openapi_core.unmarshalling.schemas.exceptions import FormatUnmarshalError -from openapi_core.unmarshalling.schemas.exceptions import UnmarshallerError from openapi_core.validation.schemas.validators import SchemaValidator log = logging.getLogger(__name__) @@ -138,34 +136,15 @@ def _unmarshal_properties( class MultiTypeUnmarshaller(PrimitiveUnmarshaller): def __call__(self, value: Any) -> Any: - unmarshaller = self._get_best_unmarshaller(value) + primitive_type = self.schema_validator.get_primitive_type(value) + unmarshaller = self.schema_unmarshaller.get_type_unmarshaller( + primitive_type + ) return unmarshaller(value) - @property - def type(self) -> List[str]: - types = self.schema.getkey("type", ["any"]) - assert isinstance(types, list) - return types - - def _get_best_unmarshaller(self, value: Any) -> "PrimitiveUnmarshaller": - for schema_type in self.type: - result = self.schema_validator.type_validator( - value, type_override=schema_type - ) - if not result: - continue - result = self.schema_validator.format_validator(value) - if not result: - continue - return self.schema_unmarshaller.get_type_unmarshaller(schema_type) - - raise UnmarshallerError("Unmarshaller not found for type(s)") - class AnyUnmarshaller(MultiTypeUnmarshaller): - @property - def type(self) -> List[str]: - return self.schema_unmarshaller.types_unmarshaller.get_types() + pass class TypesUnmarshaller: @@ -185,7 +164,7 @@ def __init__( def get_types(self) -> List[str]: return list(self.unmarshallers.keys()) - def get_unmarshaller( + def get_unmarshaller_cls( self, schema_type: Optional[Union[Iterable[str], str]], ) -> Type["PrimitiveUnmarshaller"]: @@ -220,8 +199,8 @@ def unmarshal(self, schema_format: str, value: Any) -> Any: return value try: return format_unmarshaller(value) - except (ValueError, TypeError) as exc: - raise FormatUnmarshalError(value, schema_format, exc) + except (AttributeError, ValueError, TypeError): + return value def get_unmarshaller( self, schema_format: str @@ -279,19 +258,32 @@ def unmarshal(self, value: Any) -> Any: (isinstance(value, bytes) and schema_format in ["binary", "byte"]) ): return typed - return self.formats_unmarshaller.unmarshal(schema_format, typed) + + format_unmarshaller = self.get_format_unmarshaller(schema_format) + if format_unmarshaller is None: + return typed + try: + return format_unmarshaller(typed) + except (AttributeError, ValueError, TypeError): + return typed def get_type_unmarshaller( self, schema_type: Optional[Union[Iterable[str], str]], ) -> PrimitiveUnmarshaller: - klass = self.types_unmarshaller.get_unmarshaller(schema_type) + klass = self.types_unmarshaller.get_unmarshaller_cls(schema_type) return klass( self.schema, self.schema_validator, self, ) + def get_format_unmarshaller( + self, + schema_format: str, + ) -> Optional[FormatUnmarshaller]: + return self.formats_unmarshaller.get_unmarshaller(schema_format) + def evolve(self, schema: SchemaPath) -> "SchemaUnmarshaller": cls = self.__class__ @@ -304,6 +296,10 @@ def evolve(self, schema: SchemaPath) -> "SchemaUnmarshaller": def find_format(self, value: Any) -> Optional[str]: for schema in self.schema_validator.iter_valid_schemas(value): + schema_validator = self.schema_validator.evolve(schema) + primitive_type = schema_validator.get_primitive_type(value) + if primitive_type != "string": + continue if "format" in schema: return str(schema.getkey("format")) return None diff --git a/openapi_core/validation/schemas/validators.py b/openapi_core/validation/schemas/validators.py index 6a4954e9..c822a82f 100644 --- a/openapi_core/validation/schemas/validators.py +++ b/openapi_core/validation/schemas/validators.py @@ -78,6 +78,24 @@ def format_validator_callable(self) -> FormatValidator: return lambda x: True + def get_primitive_type(self, value: Any) -> Optional[str]: + schema_types = self.schema.getkey("type") + if isinstance(schema_types, str): + return schema_types + if schema_types is None: + schema_types = sorted(self.validator.TYPE_CHECKER._type_checkers) + assert isinstance(schema_types, list) + for schema_type in schema_types: + result = self.type_validator(value, type_override=schema_type) + if not result: + continue + result = self.format_validator(value) + if not result: + continue + assert isinstance(schema_type, (str, type(None))) + return schema_type + return None + def iter_valid_schemas(self, value: Any) -> Iterator[SchemaPath]: yield self.schema diff --git a/tests/integration/unmarshalling/test_unmarshallers.py b/tests/integration/unmarshalling/test_unmarshallers.py index 04c980a6..764fc3af 100644 --- a/tests/integration/unmarshalling/test_unmarshallers.py +++ b/tests/integration/unmarshalling/test_unmarshallers.py @@ -2057,6 +2057,27 @@ def test_nultiple_types_invalid(self, unmarshallers_factory, types, value): assert len(exc_info.value.schema_errors) == 1 assert "is not of type" in exc_info.value.schema_errors[0].message + @pytest.mark.parametrize( + "types,format,value,expected", + [ + (["string", "null"], "date", None, None), + (["string", "null"], "date", "2018-12-13", date(2018, 12, 13)), + ], + ) + def test_multiple_types_format_valid_or_ignored( + self, unmarshallers_factory, types, format, value, expected + ): + schema = { + "type": types, + "format": format, + } + spec = SchemaPath.from_dict(schema) + unmarshaller = unmarshallers_factory.create(spec) + + result = unmarshaller.unmarshal(value) + + assert result == expected + def test_any_null(self, unmarshallers_factory): schema = {} spec = SchemaPath.from_dict(schema) diff --git a/tests/unit/unmarshalling/test_schema_unmarshallers.py b/tests/unit/unmarshalling/test_schema_unmarshallers.py index 3373a34f..5a8fe12e 100644 --- a/tests/unit/unmarshalling/test_schema_unmarshallers.py +++ b/tests/unit/unmarshalling/test_schema_unmarshallers.py @@ -8,7 +8,6 @@ from openapi_core.unmarshalling.schemas.exceptions import ( FormatterNotFoundError, ) -from openapi_core.unmarshalling.schemas.exceptions import FormatUnmarshalError from openapi_core.unmarshalling.schemas.factories import ( SchemaUnmarshallersFactory, ) @@ -102,8 +101,9 @@ def custom_format_unmarshaller(value): extra_format_unmarshallers=extra_format_unmarshallers, ) - with pytest.raises(FormatUnmarshalError): - unmarshaller.unmarshal(value) + result = unmarshaller.unmarshal(value) + + assert result == value def test_schema_extra_format_unmarshaller_format_custom( self, schema_unmarshaller_factory