Skip to content

Commit 3ffe2bc

Browse files
authored
Merge pull request #433 from p1c2u/feature/multi-type-unmarshaller
Multi type unmarshaller
2 parents 7db11f4 + d95fba7 commit 3ffe2bc

File tree

3 files changed

+96
-0
lines changed

3 files changed

+96
-0
lines changed

openapi_core/unmarshalling/schemas/factories.py

+10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import warnings
22
from typing import Any
33
from typing import Dict
4+
from typing import Iterable
45
from typing import Optional
56
from typing import Type
67
from typing import Union
@@ -30,6 +31,9 @@
3031
from openapi_core.unmarshalling.schemas.unmarshallers import (
3132
IntegerUnmarshaller,
3233
)
34+
from openapi_core.unmarshalling.schemas.unmarshallers import (
35+
MultiTypeUnmarshaller,
36+
)
3337
from openapi_core.unmarshalling.schemas.unmarshallers import NullUnmarshaller
3438
from openapi_core.unmarshalling.schemas.unmarshallers import NumberUnmarshaller
3539
from openapi_core.unmarshalling.schemas.unmarshallers import ObjectUnmarshaller
@@ -89,6 +93,12 @@ def create(
8993
formatter = self.custom_formatters.get(schema_format)
9094

9195
schema_type = type_override or schema.getkey("type", "any")
96+
if isinstance(schema_type, Iterable) and not isinstance(
97+
schema_type, str
98+
):
99+
return MultiTypeUnmarshaller(
100+
schema, validator, formatter, self, context=self.context
101+
)
92102
if schema_type in self.COMPLEX_UNMARSHALLERS:
93103
complex_klass = self.COMPLEX_UNMARSHALLERS[schema_type]
94104
return complex_klass(

openapi_core/unmarshalling/schemas/unmarshallers.py

+21
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,27 @@ def _unmarshal_object(self, value: Any) -> Any:
312312
return properties
313313

314314

315+
class MultiTypeUnmarshaller(ComplexUnmarshaller):
316+
@property
317+
def types_unmarshallers(self) -> List["BaseSchemaUnmarshaller"]:
318+
types = self.schema.getkey("type", ["any"])
319+
unmarshaller = partial(self.unmarshallers_factory.create, self.schema)
320+
return list(map(unmarshaller, types))
321+
322+
def unmarshal(self, value: Any) -> Any:
323+
for unmarshaller in self.types_unmarshallers:
324+
# validate with validator of formatter (usualy type validator)
325+
try:
326+
unmarshaller._formatter_validate(value)
327+
except ValidateError:
328+
continue
329+
else:
330+
return unmarshaller(value)
331+
332+
log.warning("failed to unmarshal multi type")
333+
return value
334+
335+
315336
class AnyUnmarshaller(ComplexUnmarshaller):
316337

317338
SCHEMA_TYPES_ORDER = [

tests/unit/unmarshalling/test_unmarshal.py

+65
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,34 @@ def test_additional_properties_list(self, unmarshaller_factory):
835835
"user_ids": [1, 2, 3, 4],
836836
}
837837

838+
@pytest.mark.xfail(message="None and NOTSET should be distinguished")
839+
def test_null_not_supported(self, unmarshaller_factory):
840+
schema = {"type": "null"}
841+
spec = Spec.from_dict(schema)
842+
843+
with pytest.raises(InvalidSchemaValue):
844+
unmarshaller_factory(spec)(None)
845+
846+
@pytest.mark.parametrize(
847+
"types,value",
848+
[
849+
(["string", "null"], "string"),
850+
(["number", "null"], 2),
851+
(["number", "null"], 3.14),
852+
(["boolean", "null"], True),
853+
(["array", "null"], [1, 2]),
854+
(["object", "null"], {}),
855+
],
856+
)
857+
def test_nultiple_types_not_supported(
858+
self, unmarshaller_factory, types, value
859+
):
860+
schema = {"type": types}
861+
spec = Spec.from_dict(schema)
862+
863+
with pytest.raises(TypeError):
864+
unmarshaller_factory(spec)(value)
865+
838866

839867
class TestOAS31SchemaUnmarshallerCall:
840868
@pytest.fixture
@@ -856,3 +884,40 @@ def test_null_invalid(self, unmarshaller_factory, value):
856884

857885
with pytest.raises(InvalidSchemaValue):
858886
unmarshaller_factory(spec)(value)
887+
888+
@pytest.mark.parametrize(
889+
"types,value",
890+
[
891+
(["string", "null"], "string"),
892+
(["number", "null"], 2),
893+
(["number", "null"], 3.14),
894+
(["boolean", "null"], True),
895+
(["array", "null"], [1, 2]),
896+
(["object", "null"], {}),
897+
],
898+
)
899+
def test_nultiple_types(self, unmarshaller_factory, types, value):
900+
schema = {"type": types}
901+
spec = Spec.from_dict(schema)
902+
903+
result = unmarshaller_factory(spec)(value)
904+
905+
assert result == value
906+
907+
@pytest.mark.parametrize(
908+
"types,value",
909+
[
910+
(["string", "null"], 2),
911+
(["number", "null"], "string"),
912+
(["number", "null"], True),
913+
(["boolean", "null"], 3.14),
914+
(["array", "null"], {}),
915+
(["object", "null"], [1, 2]),
916+
],
917+
)
918+
def test_nultiple_types_invalid(self, unmarshaller_factory, types, value):
919+
schema = {"type": types}
920+
spec = Spec.from_dict(schema)
921+
922+
with pytest.raises(InvalidSchemaValue):
923+
unmarshaller_factory(spec)(value)

0 commit comments

Comments
 (0)