Skip to content

Commit cf53e19

Browse files
committed
Unmarshal any schema type
1 parent 05b8a30 commit cf53e19

File tree

4 files changed

+73
-6
lines changed

4 files changed

+73
-6
lines changed

openapi_core/schema/schemas/exceptions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ class OpenAPISchemaError(OpenAPIMappingError):
55
pass
66

77

8+
class NoValidSchema(OpenAPISchemaError):
9+
pass
10+
11+
12+
class UndefinedItemsSchema(OpenAPISchemaError):
13+
pass
14+
15+
816
class InvalidSchemaValue(OpenAPISchemaError):
917
pass
1018

openapi_core/schema/schemas/models.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
1111
from openapi_core.schema.schemas.exceptions import (
1212
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
13-
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema,
13+
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema, NoValidSchema,
14+
UndefinedItemsSchema,
1415
)
1516
from openapi_core.schema.schemas.util import (
1617
forcebool, format_date, format_datetime,
@@ -26,7 +27,6 @@ class Schema(object):
2627
"""Represents an OpenAPI Schema."""
2728

2829
DEFAULT_CAST_CALLABLE_GETTER = {
29-
SchemaType.ANY: lambda x: x,
3030
SchemaType.INTEGER: int,
3131
SchemaType.NUMBER: float,
3232
SchemaType.BOOLEAN: forcebool,
@@ -47,7 +47,7 @@ class Schema(object):
4747
}
4848

4949
TYPE_VALIDATOR_CALLABLE_GETTER = {
50-
None: lambda x: True,
50+
SchemaType.ANY: lambda x: x,
5151
SchemaType.BOOLEAN: TypeValidator(bool),
5252
SchemaType.INTEGER: TypeValidator(integer_types, exclude=bool),
5353
SchemaType.NUMBER: TypeValidator(integer_types, float, exclude=bool),
@@ -108,7 +108,7 @@ def _get_all_required_properties(self):
108108

109109
return dict(
110110
(prop_name, val)
111-
for prop_name, val in all_properties.items()
111+
for prop_name, val in iteritems(all_properties)
112112
if prop_name in required
113113
)
114114

@@ -125,6 +125,7 @@ def get_cast_mapping(self):
125125
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy()
126126
mapping.update({
127127
SchemaType.STRING: self._unmarshal_string,
128+
SchemaType.ANY: self._unmarshal_any,
128129
SchemaType.ARRAY: self._unmarshal_collection,
129130
SchemaType.OBJECT: self._unmarshal_object,
130131
})
@@ -188,7 +189,26 @@ def _unmarshal_string(self, value):
188189
value, self.format)
189190
)
190191

192+
def _unmarshal_any(self, value):
193+
types_resolve_order = [
194+
SchemaType.OBJECT, SchemaType.ARRAY, SchemaType.BOOLEAN,
195+
SchemaType.INTEGER, SchemaType.NUMBER, SchemaType.STRING,
196+
]
197+
cast_mapping = self.get_cast_mapping()
198+
for schema_type in types_resolve_order:
199+
cast_callable = cast_mapping[schema_type]
200+
try:
201+
return cast_callable(value)
202+
# @todo: remove ValueError when validation separated
203+
except (OpenAPISchemaError, ValueError):
204+
pass
205+
raise NoValidSchema(
206+
"No valid schema found for value {0}".format(value))
207+
191208
def _unmarshal_collection(self, value):
209+
if self.items is None:
210+
raise UndefinedItemsSchema("Undefined items' schema")
211+
192212
return list(map(self.items.unmarshal, value))
193213

194214
def _unmarshal_object(self, value, model_factory=None):

tests/integration/data/v3.0/petstore.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,7 @@ components:
172172
oneOf:
173173
- type: string
174174
enum: [always, now]
175-
- type: string
176-
format: date-time
175+
- type: integer
177176
Address:
178177
type: object
179178
x-model: Address

tests/integration/test_petstore.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,3 +1108,43 @@ def test_post_tags_created_datetime(
11081108
assert response_result.data.message == message
11091109
assert response_result.data.rootCause == rootCause
11101110
assert response_result.data.additionalinfo == additionalinfo
1111+
1112+
def test_post_tags_created_invalid_type(
1113+
self, spec, response_validator):
1114+
host_url = 'http://petstore.swagger.io/v1'
1115+
path_pattern = '/v1/tags'
1116+
pet_name = 'Dog'
1117+
data_json = {
1118+
'created': 'long time ago',
1119+
'name': pet_name,
1120+
}
1121+
data = json.dumps(data_json)
1122+
1123+
request = MockRequest(
1124+
host_url, 'POST', '/tags',
1125+
path_pattern=path_pattern, data=data,
1126+
)
1127+
1128+
parameters = request.get_parameters(spec)
1129+
body = request.get_body(spec)
1130+
1131+
assert parameters == {}
1132+
assert body == data_json
1133+
1134+
data_json = {
1135+
'code': 400,
1136+
'message': 'Bad request',
1137+
'rootCause': 'Tag already exist',
1138+
'additionalinfo': 'Tag Dog already exist',
1139+
}
1140+
data = json.dumps(data_json)
1141+
response = MockResponse(data, status_code=404)
1142+
1143+
response_result = response_validator.validate(request, response)
1144+
1145+
assert response_result.errors == []
1146+
assert isinstance(response_result.data, BaseModel)
1147+
assert response_result.data.code == code
1148+
assert response_result.data.message == message
1149+
assert response_result.data.rootCause == rootCause
1150+
assert response_result.data.additionalinfo == additionalinfo

0 commit comments

Comments
 (0)