From cc42e15f36bc649bf109d86eba68ef0913731db0 Mon Sep 17 00:00:00 2001 From: Yarn-e Date: Tue, 16 Nov 2021 12:07:09 +0100 Subject: [PATCH] Add support for deepObject parameter style --- .../deserializing/parameters/factories.py | 2 + openapi_core/schema/parameters.py | 26 ++++++++++++- .../integration/validation/test_validators.py | 39 ++++++++++++++++++- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/openapi_core/deserializing/parameters/factories.py b/openapi_core/deserializing/parameters/factories.py index f937446f..3d87589c 100644 --- a/openapi_core/deserializing/parameters/factories.py +++ b/openapi_core/deserializing/parameters/factories.py @@ -1,3 +1,4 @@ +import re from functools import partial from typing import Dict @@ -25,6 +26,7 @@ class ParameterDeserializersFactory: "simple": partial(split, separator=","), "spaceDelimited": partial(split, separator=" "), "pipeDelimited": partial(split, separator="|"), + "deepObject": partial(re.split, pattern=r"\[|\]"), } def create(self, param_or_header: Spec) -> BaseParameterDeserializer: diff --git a/openapi_core/schema/parameters.py b/openapi_core/schema/parameters.py index 30195c67..45baf229 100644 --- a/openapi_core/schema/parameters.py +++ b/openapi_core/schema/parameters.py @@ -1,3 +1,4 @@ +import re from typing import Any from typing import Dict from typing import Optional @@ -53,16 +54,39 @@ def get_value( ) -> Any: """Returns parameter/header value from specific location""" name = name or param_or_header["name"] + style = get_style(param_or_header) if name not in location: - raise KeyError + # Only check if the name is not in the location if the style of + # the param is deepObject,this is because deepObjects will never be found + # as their key also includes the properties of the object already. + if style != "deepObject": + raise KeyError + keys_str = " ".join(location.keys()) + if not re.search(rf"{name}\[\w+\]", keys_str): + raise KeyError aslist = get_aslist(param_or_header) explode = get_explode(param_or_header) if aslist and explode: + if style == "deepObject": + return get_deep_object_value(location, name) if isinstance(location, SuportsGetAll): return location.getall(name) if isinstance(location, SuportsGetList): return location.getlist(name) return location[name] + + +def get_deep_object_value( + location: Union[Headers, Dict[str, Any]], + name: Optional[str] = None, +) -> Dict[str, Any]: + values = {} + for key, value in location.items(): + # Split the key from the brackets. + key_split = re.split(pattern=r"\[|\]", string=key) + if key_split[0] == name: + values[key_split[1]] = value + return values diff --git a/tests/integration/validation/test_validators.py b/tests/integration/validation/test_validators.py index 220d3ede..4bb00c0e 100644 --- a/tests/integration/validation/test_validators.py +++ b/tests/integration/validation/test_validators.py @@ -28,7 +28,6 @@ class TestRequestValidator: - host_url = "http://petstore.swagger.io" api_key = "12345" @@ -528,9 +527,45 @@ def test_request_override_param_uniqueness(self, spec, spec_dict): assert result.body is None assert result.parameters == Parameters() + def test_request_object_deep_object_params(self, spec, spec_dict): + # override path parameter on operation + spec_dict["paths"]["/resource"]["parameters"] = [ + { + # full valid parameter object required + "name": "paramObj", + "in": "query", + "required": True, + "schema": { + "type": "object", + "properties": { + "count": {"type": "integer"}, + "name": {"type": "string"}, + }, + }, + "explode": True, + "style": "deepObject", + } + ] + + request = MockRequest( + "http://example.com", + "get", + "/resource", + args={"paramObj[count]": 2, "paramObj[name]": "John"}, + ) + result = openapi_request_validator.validate( + spec, request, base_url="http://example.com" + ) + + assert len(result.errors) == 0 + assert result.body is None + assert len(result.parameters.query) == 1 + assert is_dataclass(result.parameters.query["paramObj"]) + assert result.parameters.query["paramObj"].count == 2 + assert result.parameters.query["paramObj"].name == "John" -class TestResponseValidator: +class TestResponseValidator: host_url = "http://petstore.swagger.io" @pytest.fixture