Skip to content

Commit

Permalink
Parameter deserialize complex scenario support
Browse files Browse the repository at this point in the history
  • Loading branch information
p1c2u committed May 16, 2021
1 parent 85cf602 commit 5d7cfdf
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 68 deletions.
8 changes: 0 additions & 8 deletions openapi_core/deserializing/parameters/factories.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import warnings

from openapi_core.deserializing.parameters.deserializers import (
PrimitiveDeserializer,
)
Expand All @@ -16,12 +14,6 @@ class ParameterDeserializersFactory(object):
}

def create(self, param):
if param.getkey('deprecated', False):
warnings.warn(
"{0} parameter is deprecated".format(param['name']),
DeprecationWarning,
)

style = get_style(param)

deserialize_callable = self.PARAMETER_STYLE_DESERIALIZERS[style]
Expand Down
74 changes: 45 additions & 29 deletions openapi_core/validation/request/validators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""OpenAPI core validation request validators module"""
from __future__ import division
from itertools import chain
import warnings

from openapi_core.casting.schemas.exceptions import CastError
from openapi_core.deserializing.exceptions import DeserializeError
Expand Down Expand Up @@ -160,41 +161,52 @@ def _get_parameters(self, request, params):
continue
seen.add((param_name, param_location))
try:
raw_value = self._get_parameter_value(param, request)
except MissingRequiredParameter as exc:
errors.append(exc)
continue
value = self._get_parameter(param, request)
except MissingParameter:
if 'schema' not in param:
continue
schema = param / 'schema'
if 'default' not in schema:
continue
casted = schema['default']
else:
try:
deserialised = self._deserialise_parameter(
param, raw_value)
except DeserializeError as exc:
errors.append(exc)
continue

try:
casted = self._cast(param, deserialised)
except CastError as exc:
errors.append(exc)
continue

try:
unmarshalled = self._unmarshal(param, casted)
except (ValidateError, UnmarshalError) as exc:
continue
except (
MissingRequiredParameter, DeserializeError,
CastError, ValidateError, UnmarshalError,
) as exc:
errors.append(exc)
continue
else:
locations.setdefault(param_location, {})
locations[param_location][param_name] = unmarshalled
locations[param_location][param_name] = value

return RequestParameters(**locations), errors

def _get_parameter(self, param, request):
if param.getkey('deprecated', False):
warnings.warn(
"{0} parameter is deprecated".format(param['name']),
DeprecationWarning,
)

try:
raw_value = self._get_parameter_value(param, request)
except MissingParameter:
if 'schema' not in param:
raise
schema = param / 'schema'
if 'default' not in schema:
raise
casted = schema['default']
else:
# Simple scenario
if 'content' not in param:
deserialised = self._deserialise_parameter(param, raw_value)
schema = param / 'schema'
# Complex scenario
else:
content = param / 'content'
mimetype, media_type = next(content.items())
deserialised = self._deserialise_data(mimetype, raw_value)
schema = media_type / 'schema'
casted = self._cast(schema, deserialised)
unmarshalled = self._unmarshal(schema, casted)
return unmarshalled

def _get_body(self, request, operation):
if 'requestBody' not in operation:
return None, []
Expand Down Expand Up @@ -224,8 +236,12 @@ def _get_body(self, request, operation):
except CastError as exc:
return None, [exc, ]

if 'schema' not in media_type:
return casted, []

schema = media_type / 'schema'
try:
body = self._unmarshal(media_type, casted)
body = self._unmarshal(schema, casted)
except (ValidateError, UnmarshalError) as exc:
return None, [exc, ]

Expand Down
6 changes: 5 additions & 1 deletion openapi_core/validation/response/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,12 @@ def _get_data(self, response, operation_response):
except CastError as exc:
return None, [exc, ]

if 'schema' not in media_type:
return casted, []

schema = media_type / 'schema'
try:
data = self._unmarshal(media_type, casted)
data = self._unmarshal(schema, casted)
except (ValidateError, UnmarshalError) as exc:
return None, [exc, ]

Expand Down
13 changes: 2 additions & 11 deletions openapi_core/validation/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,10 @@ def _deserialise_data(self, mimetype, value):
deserializer = self.media_type_deserializers_factory.create(mimetype)
return deserializer(value)

def _cast(self, param_or_media_type, value):
# return param_or_media_type.cast(value)
if 'schema' not in param_or_media_type:
return value

schema = param_or_media_type / 'schema'
def _cast(self, schema, value):
caster = self.schema_casters_factory.create(schema)
return caster(value)

def _unmarshal(self, param_or_media_type, value):
if 'schema' not in param_or_media_type:
return value

schema = param_or_media_type / 'schema'
def _unmarshal(self, schema, value):
unmarshaller = self.schema_unmarshallers_factory.create(schema)
return unmarshaller(value)
25 changes: 23 additions & 2 deletions tests/integration/data/v3.0/petstore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,17 @@ paths:
type: object
required:
- lat
- long
- lon
properties:
lat:
type: number
long:
lon:
type: number
responses:
'200':
$ref: "#/components/responses/PetsResponse"
'404':
$ref: "#/components/responses/HtmlResponse"
post:
summary: Create a pet
description: Creates new pet entry
Expand Down Expand Up @@ -119,6 +121,13 @@ paths:
type: integer
format: int32
required: true
- name: userdata
in: cookie
content:
application/json:
schema:
$ref: '#/components/schemas/Userdata'
required: false
requestBody:
required: true
content:
Expand All @@ -128,6 +137,7 @@ paths:
example:
name: "Pet"
wings: []
text/plain: {}
responses:
'201':
description: Null response
Expand Down Expand Up @@ -220,6 +230,13 @@ paths:
$ref: "#/components/responses/ErrorResponse"
components:
schemas:
Userdata:
type: object
required:
- name
properties:
name:
type: string
Utctime:
oneOf:
- type: string
Expand Down Expand Up @@ -406,6 +423,10 @@ components:
application/json:
schema:
$ref: "#/components/schemas/ExtendedError"
HtmlResponse:
description: HTML page
content:
text/html: {}
PetsResponse:
description: An paged array of pets
headers:
Expand Down
43 changes: 40 additions & 3 deletions tests/integration/validation/test_petstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,38 @@ def test_get_pets_response(self, spec, response_validator):
assert response_result.data.data[0].id == 1
assert response_result.data.data[0].name == 'Cat'

def test_get_pets_response_no_schema(self, spec, response_validator):
host_url = 'http://petstore.swagger.io/v1'
path_pattern = '/v1/pets'
query_params = {
'limit': '20',
}

request = MockRequest(
host_url, 'GET', '/pets',
path_pattern=path_pattern, args=query_params,
)

parameters = validate_parameters(spec, request)
body = validate_body(spec, request)

assert parameters == RequestParameters(
query={
'limit': 20,
'page': 1,
'search': '',
}
)
assert body is None

data = '<html></html>'
response = MockResponse(data, status_code=404, mimetype='text/html')

response_result = response_validator.validate(request, response)

assert response_result.errors == []
assert response_result.data == data

def test_get_pets_invalid_response(self, spec, response_validator):
host_url = 'http://petstore.swagger.io/v1'
path_pattern = '/v1/pets'
Expand Down Expand Up @@ -393,9 +425,6 @@ def test_get_pets_param_order(self, spec):

assert body is None

@pytest.mark.xfail(
reason="No parameters deserialization support for complex scenarios"
)
def test_get_pets_param_coordinates(self, spec):
host_url = 'http://petstore.swagger.io/v1'
path_pattern = '/v1/pets'
Expand Down Expand Up @@ -453,8 +482,13 @@ def test_post_birds(self, spec, spec_dict):
headers = {
'api_key': self.api_key_encoded,
}
userdata = {
'name': 'user1',
}
userdata_json = json.dumps(userdata)
cookies = {
'user': '123',
'userdata': userdata_json,
}

request = MockRequest(
Expand All @@ -471,6 +505,9 @@ def test_post_birds(self, spec, spec_dict):
},
cookie={
'user': 123,
'userdata': {
'name': 'user1',
},
},
)

Expand Down
Loading

0 comments on commit 5d7cfdf

Please sign in to comment.