Skip to content

Commit 355ce5b

Browse files
committed
fix: OAS 3.1 multi type (cast/deserialize)
1 parent 3b8a0c0 commit 355ce5b

8 files changed

Lines changed: 84 additions & 13 deletions

File tree

openapi_core/casting/schemas/casters.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ def cast(self, value: Any) -> Any:
235235
):
236236
return value
237237

238-
schema_type = (self.schema / "type").read_str(None)
238+
schema_type = (self.schema / "type").read_str_or_list(None)
239239
type_caster = self.get_type_caster(schema_type)
240240

241241
if value is None:

openapi_core/casting/schemas/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class CastError(DeserializeError):
99
"""Schema cast operation error"""
1010

1111
value: Any
12-
type: str | None
12+
type: str | list[str] | None
1313

1414
def __str__(self) -> str:
1515
return f"Failed to cast value to {self.type} type: {self.value}"

openapi_core/deserializing/styles/datatypes.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@
33
from typing import Dict
44
from typing import Mapping
55

6-
DeserializerCallable = Callable[[bool, str, str, Mapping[str, Any]], Any]
6+
DeserializerCallable = Callable[
7+
[bool, str, str | list[str], Mapping[str, Any]], Any
8+
]
79
StyleDeserializersDict = Dict[str, DeserializerCallable]

openapi_core/deserializing/styles/deserializers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def __init__(
1515
style: str,
1616
explode: bool,
1717
name: str,
18-
schema_type: str,
18+
schema_type: str | list[str],
1919
caster: SchemaCaster,
2020
deserializer_callable: Optional[DeserializerCallable] = None,
2121
):

openapi_core/deserializing/styles/factories.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def create(
2828
) -> StyleDeserializer:
2929
deserialize_callable = self.style_deserializers.get(style)
3030
caster = self.schema_casters_factory.create(spec, schema)
31-
schema_type = (schema / "type").read_str("")
31+
schema_type = (schema / "type").read_str_or_list("")
3232
return StyleDeserializer(
3333
style, explode, name, schema_type, caster, deserialize_callable
3434
)

openapi_core/deserializing/styles/util.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def split(value: str, separator: str = ",", step: int = 1) -> List[str]:
2525
def delimited_loads(
2626
explode: bool,
2727
name: str,
28-
schema_type: str,
28+
schema_type: str | list[str],
2929
location: Mapping[str, Any],
3030
delimiter: str,
3131
) -> Any:
@@ -46,7 +46,10 @@ def delimited_loads(
4646

4747

4848
def matrix_loads(
49-
explode: bool, name: str, schema_type: str, location: Mapping[str, Any]
49+
explode: bool,
50+
name: str,
51+
schema_type: str | list[str],
52+
location: Mapping[str, Any],
5053
) -> Any:
5154
if explode == False:
5255
m = re.match(rf"^;{name}=(.*)$", location[f";{name}"])
@@ -83,7 +86,10 @@ def matrix_loads(
8386

8487

8588
def label_loads(
86-
explode: bool, name: str, schema_type: str, location: Mapping[str, Any]
89+
explode: bool,
90+
name: str,
91+
schema_type: str | list[str],
92+
location: Mapping[str, Any],
8793
) -> Any:
8894
if explode == False:
8995
value = location[f".{name}"]
@@ -113,7 +119,10 @@ def label_loads(
113119

114120

115121
def form_loads(
116-
explode: bool, name: str, schema_type: str, location: Mapping[str, Any]
122+
explode: bool,
123+
name: str,
124+
schema_type: str | list[str],
125+
location: Mapping[str, Any],
117126
) -> Any:
118127
explode_type = (explode, schema_type)
119128
# color=blue,black,brown
@@ -144,7 +153,10 @@ def form_loads(
144153

145154

146155
def simple_loads(
147-
explode: bool, name: str, schema_type: str, location: Mapping[str, Any]
156+
explode: bool,
157+
name: str,
158+
schema_type: str | list[str],
159+
location: Mapping[str, Any],
148160
) -> Any:
149161
value = location[name]
150162

@@ -167,21 +179,30 @@ def simple_loads(
167179

168180

169181
def space_delimited_loads(
170-
explode: bool, name: str, schema_type: str, location: Mapping[str, Any]
182+
explode: bool,
183+
name: str,
184+
schema_type: str | list[str],
185+
location: Mapping[str, Any],
171186
) -> Any:
172187
return delimited_loads(
173188
explode, name, schema_type, location, delimiter="%20"
174189
)
175190

176191

177192
def pipe_delimited_loads(
178-
explode: bool, name: str, schema_type: str, location: Mapping[str, Any]
193+
explode: bool,
194+
name: str,
195+
schema_type: str | list[str],
196+
location: Mapping[str, Any],
179197
) -> Any:
180198
return delimited_loads(explode, name, schema_type, location, delimiter="|")
181199

182200

183201
def deep_object_loads(
184-
explode: bool, name: str, schema_type: str, location: Mapping[str, Any]
202+
explode: bool,
203+
name: str,
204+
schema_type: str | list[str],
205+
location: Mapping[str, Any],
185206
) -> Any:
186207
explode_type = (explode, schema_type)
187208

tests/unit/casting/test_schema_casters.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,26 @@ def test_array_invalid_value(self, value, caster_factory):
6767
):
6868
caster_factory(schema).cast(value)
6969

70+
@pytest.mark.parametrize(
71+
"schema_types,value",
72+
[
73+
(["string", "number", "boolean"], "12567"),
74+
(["integer", "string"], "42"),
75+
(["number", "string"], "3.14"),
76+
(["boolean", "string"], "true"),
77+
],
78+
)
79+
def test_oas31_multi_type(self, caster_factory, schema_types, value):
80+
"""Test OAS 3.1 list-style `type`."""
81+
spec = {
82+
"type": schema_types,
83+
}
84+
schema = SchemaPath.from_dict(spec)
85+
86+
result = caster_factory(schema).cast(value)
87+
88+
assert result == value
89+
7090
@pytest.mark.parametrize(
7191
"composite_type,schema_type,value,expected",
7292
[

tests/unit/deserializing/test_styles_deserializers.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,34 @@ def test_pipe_delimited_valid(
444444

445445
assert result == expected
446446

447+
@pytest.mark.parametrize(
448+
"schema_types,value,expected",
449+
[
450+
(["string", "number", "boolean"], "12567", "12567"),
451+
(["integer", "string"], "42", "42"),
452+
],
453+
)
454+
def test_oas31_multi_type_form(
455+
self, deserializer_factory, schema_types, value, expected
456+
):
457+
"""Test OAS 3.1 multi-type support for form style parameters."""
458+
name = "param"
459+
spec = {
460+
"name": name,
461+
"in": "query",
462+
"explode": True,
463+
"schema": {
464+
"type": schema_types,
465+
},
466+
}
467+
param = SchemaPath.from_dict(spec)
468+
deserializer = deserializer_factory(param)
469+
location = {name: value}
470+
471+
result = deserializer.deserialize(location)
472+
473+
assert result == expected
474+
447475
def test_deep_object_valid(self, deserializer_factory):
448476
name = "param"
449477
spec = {

0 commit comments

Comments
 (0)