Skip to content

Commit d958fe8

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

File tree

3 files changed

+111
-16
lines changed

3 files changed

+111
-16
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: 28 additions & 7 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
})
@@ -153,9 +154,6 @@ def cast(self, value):
153154

154155
def unmarshal(self, value):
155156
"""Unmarshal parameter from the value."""
156-
if self.deprecated:
157-
warnings.warn(
158-
"The schema is deprecated", DeprecationWarning)
159157
casted = self.cast(value)
160158

161159
if casted is None and not self.required:
@@ -188,7 +186,27 @@ def _unmarshal_string(self, value):
188186
value, self.format)
189187
)
190188

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

194212
def _unmarshal_object(self, value, model_factory=None):
@@ -272,6 +290,9 @@ def get_validator_mapping(self):
272290
return defaultdict(lambda: lambda x: x, mapping)
273291

274292
def validate(self, value):
293+
if self.deprecated:
294+
warnings.warn("The schema is deprecated", DeprecationWarning)
295+
275296
if value is None:
276297
if not self.nullable:
277298
raise InvalidSchemaValue("Null value for non-nullable schema")

tests/integration/test_petstore.py

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from openapi_core.schema.responses.models import Response
1818
from openapi_core.schema.schemas.exceptions import (
1919
UndefinedSchemaProperty, MissingSchemaProperty, NoOneOfSchema,
20+
NoValidSchema,
2021
)
2122
from openapi_core.schema.schemas.models import Schema
2223
from openapi_core.schema.servers.exceptions import InvalidServer
@@ -1037,9 +1038,10 @@ def test_post_tags_created_now(
10371038
self, spec, response_validator):
10381039
host_url = 'http://petstore.swagger.io/v1'
10391040
path_pattern = '/v1/tags'
1041+
created = 'now'
10401042
pet_name = 'Dog'
10411043
data_json = {
1042-
'created': 'now',
1044+
'created': created,
10431045
'name': pet_name,
10441046
}
10451047
data = json.dumps(data_json)
@@ -1053,8 +1055,14 @@ def test_post_tags_created_now(
10531055
body = request.get_body(spec)
10541056

10551057
assert parameters == {}
1056-
assert body == data_json
1058+
assert isinstance(body, BaseModel)
1059+
assert body.created == created
1060+
assert body.name == pet_name
10571061

1062+
code = 400
1063+
message = 'Bad request'
1064+
rootCause = 'Tag already exist'
1065+
additionalinfo = 'Tag Dog already exist'
10581066
data_json = {
10591067
'code': 400,
10601068
'message': 'Bad request',
@@ -1067,15 +1075,20 @@ def test_post_tags_created_now(
10671075
response_result = response_validator.validate(request, response)
10681076

10691077
assert response_result.errors == []
1070-
assert response_result.data == data_json
1078+
assert isinstance(response_result.data, BaseModel)
1079+
assert response_result.data.code == code
1080+
assert response_result.data.message == message
1081+
assert response_result.data.rootCause == rootCause
1082+
assert response_result.data.additionalinfo == additionalinfo
10711083

10721084
def test_post_tags_created_datetime(
10731085
self, spec, response_validator):
10741086
host_url = 'http://petstore.swagger.io/v1'
10751087
path_pattern = '/v1/tags'
1088+
created = '2016-04-16T16:06:05Z'
10761089
pet_name = 'Dog'
10771090
data_json = {
1078-
'created': '2016-04-16T16:06:05Z',
1091+
'created': created,
10791092
'name': pet_name,
10801093
}
10811094
data = json.dumps(data_json)
@@ -1089,17 +1102,70 @@ def test_post_tags_created_datetime(
10891102
body = request.get_body(spec)
10901103

10911104
assert parameters == {}
1092-
assert body == data_json
1105+
assert isinstance(body, BaseModel)
1106+
assert body.created == created
1107+
assert body.name == pet_name
10931108

1109+
code = 400
1110+
message = 'Bad request'
1111+
rootCause = 'Tag already exist'
1112+
additionalinfo = 'Tag Dog already exist'
10941113
data_json = {
1095-
'code': 400,
1096-
'message': 'Bad request',
1097-
'rootCause': 'Tag already exist',
1098-
'additionalinfo': 'Tag Dog already exist',
1114+
'code': code,
1115+
'message': message,
1116+
'rootCause': rootCause,
1117+
'additionalinfo': additionalinfo,
1118+
}
1119+
data = json.dumps(data_json)
1120+
response = MockResponse(data, status_code=404)
1121+
1122+
response_result = response_validator.validate(request, response)
1123+
1124+
assert response_result.errors == []
1125+
assert isinstance(response_result.data, BaseModel)
1126+
assert response_result.data.code == code
1127+
assert response_result.data.message == message
1128+
assert response_result.data.rootCause == rootCause
1129+
assert response_result.data.additionalinfo == additionalinfo
1130+
1131+
@pytest.mark.xfail(reason='OneOf for string not supported atm')
1132+
def test_post_tags_created_invalid_type(
1133+
self, spec, response_validator):
1134+
host_url = 'http://petstore.swagger.io/v1'
1135+
path_pattern = '/v1/tags'
1136+
created = 'long time ago'
1137+
pet_name = 'Dog'
1138+
data_json = {
1139+
'created': created,
1140+
'name': pet_name,
1141+
}
1142+
data = json.dumps(data_json)
1143+
1144+
request = MockRequest(
1145+
host_url, 'POST', '/tags',
1146+
path_pattern=path_pattern, data=data,
1147+
)
1148+
1149+
parameters = request.get_parameters(spec)
1150+
with pytest.raises(NoValidSchema):
1151+
request.get_body(spec)
1152+
1153+
assert parameters == {}
1154+
1155+
code = 400
1156+
message = 'Bad request'
1157+
rootCause = 'Tag already exist'
1158+
additionalinfo = 'Tag Dog already exist'
1159+
data_json = {
1160+
'code': code,
1161+
'message': message,
1162+
'rootCause': rootCause,
1163+
'additionalinfo': additionalinfo,
10991164
}
11001165
data = json.dumps(data_json)
11011166
response = MockResponse(data, status_code=404)
11021167

1168+
import ipdb; ipdb.set_trace()
11031169
response_result = response_validator.validate(request, response)
11041170

11051171
assert response_result.errors == []

0 commit comments

Comments
 (0)