Skip to content

Commit c4fab4c

Browse files
authored
Merge pull request #329 from p1c2u/feature/parameter-deserialize-complex-scenario-support
Parameter deserialize complex scenario support
2 parents 85cf602 + 20f570d commit c4fab4c

File tree

8 files changed

+204
-68
lines changed

8 files changed

+204
-68
lines changed

openapi_core/deserializing/parameters/factories.py

-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import warnings
2-
31
from openapi_core.deserializing.parameters.deserializers import (
42
PrimitiveDeserializer,
53
)
@@ -16,12 +14,6 @@ class ParameterDeserializersFactory(object):
1614
}
1715

1816
def create(self, param):
19-
if param.getkey('deprecated', False):
20-
warnings.warn(
21-
"{0} parameter is deprecated".format(param['name']),
22-
DeprecationWarning,
23-
)
24-
2517
style = get_style(param)
2618

2719
deserialize_callable = self.PARAMETER_STYLE_DESERIALIZERS[style]

openapi_core/validation/request/validators.py

+45-29
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""OpenAPI core validation request validators module"""
22
from __future__ import division
33
from itertools import chain
4+
import warnings
45

56
from openapi_core.casting.schemas.exceptions import CastError
67
from openapi_core.deserializing.exceptions import DeserializeError
@@ -160,41 +161,52 @@ def _get_parameters(self, request, params):
160161
continue
161162
seen.add((param_name, param_location))
162163
try:
163-
raw_value = self._get_parameter_value(param, request)
164-
except MissingRequiredParameter as exc:
165-
errors.append(exc)
166-
continue
164+
value = self._get_parameter(param, request)
167165
except MissingParameter:
168-
if 'schema' not in param:
169-
continue
170-
schema = param / 'schema'
171-
if 'default' not in schema:
172-
continue
173-
casted = schema['default']
174-
else:
175-
try:
176-
deserialised = self._deserialise_parameter(
177-
param, raw_value)
178-
except DeserializeError as exc:
179-
errors.append(exc)
180-
continue
181-
182-
try:
183-
casted = self._cast(param, deserialised)
184-
except CastError as exc:
185-
errors.append(exc)
186-
continue
187-
188-
try:
189-
unmarshalled = self._unmarshal(param, casted)
190-
except (ValidateError, UnmarshalError) as exc:
166+
continue
167+
except (
168+
MissingRequiredParameter, DeserializeError,
169+
CastError, ValidateError, UnmarshalError,
170+
) as exc:
191171
errors.append(exc)
172+
continue
192173
else:
193174
locations.setdefault(param_location, {})
194-
locations[param_location][param_name] = unmarshalled
175+
locations[param_location][param_name] = value
195176

196177
return RequestParameters(**locations), errors
197178

179+
def _get_parameter(self, param, request):
180+
if param.getkey('deprecated', False):
181+
warnings.warn(
182+
"{0} parameter is deprecated".format(param['name']),
183+
DeprecationWarning,
184+
)
185+
186+
try:
187+
raw_value = self._get_parameter_value(param, request)
188+
except MissingParameter:
189+
if 'schema' not in param:
190+
raise
191+
schema = param / 'schema'
192+
if 'default' not in schema:
193+
raise
194+
casted = schema['default']
195+
else:
196+
# Simple scenario
197+
if 'content' not in param:
198+
deserialised = self._deserialise_parameter(param, raw_value)
199+
schema = param / 'schema'
200+
# Complex scenario
201+
else:
202+
content = param / 'content'
203+
mimetype, media_type = next(content.items())
204+
deserialised = self._deserialise_data(mimetype, raw_value)
205+
schema = media_type / 'schema'
206+
casted = self._cast(schema, deserialised)
207+
unmarshalled = self._unmarshal(schema, casted)
208+
return unmarshalled
209+
198210
def _get_body(self, request, operation):
199211
if 'requestBody' not in operation:
200212
return None, []
@@ -224,8 +236,12 @@ def _get_body(self, request, operation):
224236
except CastError as exc:
225237
return None, [exc, ]
226238

239+
if 'schema' not in media_type:
240+
return casted, []
241+
242+
schema = media_type / 'schema'
227243
try:
228-
body = self._unmarshal(media_type, casted)
244+
body = self._unmarshal(schema, casted)
229245
except (ValidateError, UnmarshalError) as exc:
230246
return None, [exc, ]
231247

openapi_core/validation/response/validators.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,12 @@ def _get_data(self, response, operation_response):
105105
except CastError as exc:
106106
return None, [exc, ]
107107

108+
if 'schema' not in media_type:
109+
return casted, []
110+
111+
schema = media_type / 'schema'
108112
try:
109-
data = self._unmarshal(media_type, casted)
113+
data = self._unmarshal(schema, casted)
110114
except (ValidateError, UnmarshalError) as exc:
111115
return None, [exc, ]
112116

openapi_core/validation/validators.py

+2-11
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,10 @@ def _deserialise_data(self, mimetype, value):
5252
deserializer = self.media_type_deserializers_factory.create(mimetype)
5353
return deserializer(value)
5454

55-
def _cast(self, param_or_media_type, value):
56-
# return param_or_media_type.cast(value)
57-
if 'schema' not in param_or_media_type:
58-
return value
59-
60-
schema = param_or_media_type / 'schema'
55+
def _cast(self, schema, value):
6156
caster = self.schema_casters_factory.create(schema)
6257
return caster(value)
6358

64-
def _unmarshal(self, param_or_media_type, value):
65-
if 'schema' not in param_or_media_type:
66-
return value
67-
68-
schema = param_or_media_type / 'schema'
59+
def _unmarshal(self, schema, value):
6960
unmarshaller = self.schema_unmarshallers_factory.create(schema)
7061
return unmarshaller(value)

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

+23-2
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,17 @@ paths:
8383
type: object
8484
required:
8585
- lat
86-
- long
86+
- lon
8787
properties:
8888
lat:
8989
type: number
90-
long:
90+
lon:
9191
type: number
9292
responses:
9393
'200':
9494
$ref: "#/components/responses/PetsResponse"
95+
'404':
96+
$ref: "#/components/responses/HtmlResponse"
9597
post:
9698
summary: Create a pet
9799
description: Creates new pet entry
@@ -119,6 +121,13 @@ paths:
119121
type: integer
120122
format: int32
121123
required: true
124+
- name: userdata
125+
in: cookie
126+
content:
127+
application/json:
128+
schema:
129+
$ref: '#/components/schemas/Userdata'
130+
required: false
122131
requestBody:
123132
required: true
124133
content:
@@ -128,6 +137,7 @@ paths:
128137
example:
129138
name: "Pet"
130139
wings: []
140+
text/plain: {}
131141
responses:
132142
'201':
133143
description: Null response
@@ -220,6 +230,13 @@ paths:
220230
$ref: "#/components/responses/ErrorResponse"
221231
components:
222232
schemas:
233+
Userdata:
234+
type: object
235+
required:
236+
- name
237+
properties:
238+
name:
239+
type: string
223240
Utctime:
224241
oneOf:
225242
- type: string
@@ -406,6 +423,10 @@ components:
406423
application/json:
407424
schema:
408425
$ref: "#/components/schemas/ExtendedError"
426+
HtmlResponse:
427+
description: HTML page
428+
content:
429+
text/html: {}
409430
PetsResponse:
410431
description: An paged array of pets
411432
headers:

tests/integration/validation/test_petstore.py

+40-3
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,38 @@ def test_get_pets_response(self, spec, response_validator):
139139
assert response_result.data.data[0].id == 1
140140
assert response_result.data.data[0].name == 'Cat'
141141

142+
def test_get_pets_response_no_schema(self, spec, response_validator):
143+
host_url = 'http://petstore.swagger.io/v1'
144+
path_pattern = '/v1/pets'
145+
query_params = {
146+
'limit': '20',
147+
}
148+
149+
request = MockRequest(
150+
host_url, 'GET', '/pets',
151+
path_pattern=path_pattern, args=query_params,
152+
)
153+
154+
parameters = validate_parameters(spec, request)
155+
body = validate_body(spec, request)
156+
157+
assert parameters == RequestParameters(
158+
query={
159+
'limit': 20,
160+
'page': 1,
161+
'search': '',
162+
}
163+
)
164+
assert body is None
165+
166+
data = '<html></html>'
167+
response = MockResponse(data, status_code=404, mimetype='text/html')
168+
169+
response_result = response_validator.validate(request, response)
170+
171+
assert response_result.errors == []
172+
assert response_result.data == data
173+
142174
def test_get_pets_invalid_response(self, spec, response_validator):
143175
host_url = 'http://petstore.swagger.io/v1'
144176
path_pattern = '/v1/pets'
@@ -393,9 +425,6 @@ def test_get_pets_param_order(self, spec):
393425

394426
assert body is None
395427

396-
@pytest.mark.xfail(
397-
reason="No parameters deserialization support for complex scenarios"
398-
)
399428
def test_get_pets_param_coordinates(self, spec):
400429
host_url = 'http://petstore.swagger.io/v1'
401430
path_pattern = '/v1/pets'
@@ -453,8 +482,13 @@ def test_post_birds(self, spec, spec_dict):
453482
headers = {
454483
'api_key': self.api_key_encoded,
455484
}
485+
userdata = {
486+
'name': 'user1',
487+
}
488+
userdata_json = json.dumps(userdata)
456489
cookies = {
457490
'user': '123',
491+
'userdata': userdata_json,
458492
}
459493

460494
request = MockRequest(
@@ -471,6 +505,9 @@ def test_post_birds(self, spec, spec_dict):
471505
},
472506
cookie={
473507
'user': 123,
508+
'userdata': {
509+
'name': 'user1',
510+
},
474511
},
475512
)
476513

0 commit comments

Comments
 (0)