Repository: python-openapi/openapi-core (language: python)

Issue:
Multi-query parameter validation not working
Validation for query parameter with multiple values (i.e. array values in form-style query expansion) not working, at least from version `0.13.3` of `openapi-core`.

Example request:
```
http://localhost:8000/v1/test?a=1&a=2
```

Appropriate OpenAPI spec to validate above parameters as `a = ['1', '2']`:
```
parameters:
  - in: query
    name: a
    description: Test query parameter.
    schema:
      type: array
      items:
        type: string
    required: true
```

Validation error:
```
openapi_core.unmarshalling.schemas.exceptions.InvalidSchemaValue: Value [['1', '2']] not valid for schema of type array: (<ValidationError: "['1', '2'] is not of type string">,)
```

Till version `0.13.8` it was possible to "cheat" validation by describing schema as:
```
parameters:
  - in: query
    name: a
    description: Test query parameter.
    schema: {}
    required: true
```

Unfortunately, above workaround not working anymore (from version `0.14.0`), but strangely, schema can be described as follows:
```
parameters:
  - in: query
    name: a
    description: Test query parameter.
    schema:
      type: array
      items:
        type: array
        items:
          type: string
    required: true
```

However, this is not proper way to describe query parameter with multiple values and should be fixed!


Discussion / hints:
It must be something with the integration. I assume you use Falcon one?
Yes, it's Falcon, little older version, `1.4.1`. Could not remember did I test the latest Falcon version, but I will check and report here.
In meantime, I migrated to the latest `openapi-core` version, i.e. `0.19.0`, with latest Falcon integration (`3.1.1`).

Following still working partially:
```
parameters:
  - in: query
    name: a
    description: Test query parameter.
    schema:
      type: array
      items:
        type: array
        items:
          type: string
    required: true
```
"Partially" because it failing if I pass only one  parameter (in all other combinations it working fine).

However, my question again is - why valid OpenAPI 3 description is not supported:
```
parameters:
  - in: query
    name: a
    description: Test query parameter.
    schema:
      type: array
      items:
        type: string
    required: true
```

Falcon properly decodes such request, for example request `/v1/test?a=1&a=2` will be unpacked as `{'a': ['1', '2']}` (Falcon request params), but `openapi-core` reports validation error:
```
openapi_core.validation.schemas.exceptions.InvalidSchemaValue: Value [['1', '2']] not valid for schema of type array: (<ValidationError: "['1', '2'] is not of type 'string'">,)
```

Am I missing something here?
@p1c2u I apologize for making noise, but how can I properly describe multi-value query parameters in OpenAPI 3 spec so that it working fine with `openapi-core` validation with Falcon integration (all the most recent versions)?

It must be that I'm doing something wrong, but not sure what!?
I think I found the reason of this issue. I will prepare fix for that.

Fix patch:
diff --git a/openapi_core/contrib/falcon/requests.py b/openapi_core/contrib/falcon/requests.py
index 6ef5033e..7e1fe1cf 100644
--- a/openapi_core/contrib/falcon/requests.py
+++ b/openapi_core/contrib/falcon/requests.py
@@ -11,6 +11,7 @@
 from werkzeug.datastructures import Headers
 from werkzeug.datastructures import ImmutableMultiDict
 
+from openapi_core.contrib.falcon.util import unpack_params
 from openapi_core.datatypes import RequestParameters
 
 
@@ -29,7 +30,7 @@ def __init__(
 
         # Path gets deduced by path finder against spec
         self.parameters = RequestParameters(
-            query=ImmutableMultiDict(list(self.request.params.items())),
+            query=ImmutableMultiDict(unpack_params(self.request.params)),
             header=Headers(self.request.headers),
             cookie=self.request.cookies,
         )
diff --git a/openapi_core/contrib/falcon/util.py b/openapi_core/contrib/falcon/util.py
new file mode 100644
index 00000000..0f651e42
--- /dev/null
+++ b/openapi_core/contrib/falcon/util.py
@@ -0,0 +1,15 @@
+from typing import Any
+from typing import Dict
+from typing import Generator
+from typing import Tuple
+
+
+def unpack_params(
+    params: Dict[str, Any]
+) -> Generator[Tuple[str, Any], None, None]:
+    for k, v in params.items():
+        if isinstance(v, list):
+            for v2 in v:
+                yield (k, v2)
+        else:
+            yield (k, v)


Test patch:
diff --git a/tests/integration/contrib/falcon/data/v3.0/falconproject/pets/resources.py b/tests/integration/contrib/falcon/data/v3.0/falconproject/pets/resources.py
index 5d0a83f4..d6e903da 100644
--- a/tests/integration/contrib/falcon/data/v3.0/falconproject/pets/resources.py
+++ b/tests/integration/contrib/falcon/data/v3.0/falconproject/pets/resources.py
@@ -11,11 +11,19 @@ class PetListResource:
     def on_get(self, request, response):
         assert request.context.openapi
         assert not request.context.openapi.errors
-        assert request.context.openapi.parameters.query == {
-            "page": 1,
-            "limit": 12,
-            "search": "",
-        }
+        if "ids" in request.params:
+            assert request.context.openapi.parameters.query == {
+                "page": 1,
+                "limit": 2,
+                "search": "",
+                "ids": [1, 2],
+            }
+        else:
+            assert request.context.openapi.parameters.query == {
+                "page": 1,
+                "limit": 12,
+                "search": "",
+            }
         data = [
             {
                 "id": 12,
diff --git a/tests/integration/contrib/falcon/test_falcon_project.py b/tests/integration/contrib/falcon/test_falcon_project.py
index 7ed3a19c..69e11974 100644
--- a/tests/integration/contrib/falcon/test_falcon_project.py
+++ b/tests/integration/contrib/falcon/test_falcon_project.py
@@ -67,6 +67,33 @@ def test_get_valid(self, client):
             ],
         }
 
+    def test_get_valid_multiple_ids(self, client):
+        headers = {
+            "Content-Type": "application/json",
+        }
+        query_string = "limit=2&ids=1&ids=2"
+
+        with pytest.warns(DeprecationWarning):
+            response = client.simulate_get(
+                "/v1/pets",
+                host="petstore.swagger.io",
+                headers=headers,
+                query_string=query_string,
+            )
+
+        assert response.status_code == 200
+        assert response.json == {
+            "data": [
+                {
+                    "id": 12,
+                    "name": "Cat",
+                    "ears": {
+                        "healthy": True,
+                    },
+                },
+            ],
+        }
+
     def test_post_server_invalid(self, client):
         response = client.simulate_post(
             "/v1/pets",