Skip to content

Commit b72a9dd

Browse files
committed
[verified] Fix empty query parameter schema validation
1 parent 0337b43 commit b72a9dd

3 files changed

Lines changed: 117 additions & 12 deletions

File tree

openapi_core/validation/validators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ def _get_simple_param_or_header(
249249
"Use of allowEmptyValue property is deprecated",
250250
DeprecationWarning,
251251
)
252-
if allow_empty_values is None or not allow_empty_values:
252+
if allow_empty_values is False:
253253
# if "in" not defined then it's a Header
254254
location_name = (param_or_header / "in").read_str("header")
255255
if (
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
from typing import Optional
2+
3+
import pytest
4+
5+
from openapi_core import OpenAPI
6+
from openapi_core.deserializing.styles.exceptions import (
7+
EmptyQueryParameterValue,
8+
)
9+
from openapi_core.testing import MockRequest
10+
from openapi_core.validation.request.exceptions import InvalidParameter
11+
from openapi_core.validation.request.exceptions import ParameterValidationError
12+
from openapi_core.validation.schemas.exceptions import InvalidSchemaValue
13+
14+
OPENAPI_VERSIONS = ["3.0.4", "3.1.1", "3.2.0"]
15+
16+
17+
def make_openapi(openapi_version, schema, **parameter_options):
18+
return OpenAPI.from_dict(
19+
{
20+
"openapi": openapi_version,
21+
"info": {"title": "Empty query parameter", "version": "1.0.0"},
22+
"paths": {
23+
"/api": {
24+
"get": {
25+
"parameters": [
26+
{
27+
"name": "status",
28+
"in": "query",
29+
"schema": schema,
30+
**parameter_options,
31+
}
32+
],
33+
"responses": {"200": {"description": "OK"}},
34+
}
35+
}
36+
},
37+
}
38+
)
39+
40+
41+
def make_request(value: Optional[str] = ""):
42+
args = {} if value is None else {"status": value}
43+
return MockRequest("http://localhost", "get", "/api", args=args)
44+
45+
46+
@pytest.mark.parametrize("openapi_version", OPENAPI_VERSIONS)
47+
@pytest.mark.parametrize(
48+
"schema", [{"enum": ["Active", ""]}, {"type": "string"}]
49+
)
50+
def test_empty_query_parameter_allowed_by_schema(openapi_version, schema):
51+
openapi = make_openapi(openapi_version, schema)
52+
request = make_request()
53+
54+
assert list(openapi.iter_request_errors(request)) == []
55+
result = openapi.unmarshal_request(request)
56+
assert result.errors == []
57+
assert result.parameters.query == {"status": ""}
58+
59+
60+
@pytest.mark.parametrize("openapi_version", OPENAPI_VERSIONS)
61+
@pytest.mark.parametrize(
62+
"schema", [{"enum": ["Active"]}, {"type": "string", "minLength": 1}]
63+
)
64+
def test_empty_query_parameter_rejected_by_schema(openapi_version, schema):
65+
openapi = make_openapi(openapi_version, schema)
66+
request = make_request()
67+
68+
validation_errors = list(openapi.iter_request_errors(request))
69+
assert len(validation_errors) == 1
70+
assert type(validation_errors[0]) is InvalidParameter
71+
assert type(validation_errors[0].__cause__) is InvalidSchemaValue
72+
73+
result = openapi.unmarshal_request(request)
74+
errors = list(result.errors)
75+
assert len(errors) == 1
76+
assert type(errors[0]) is InvalidParameter
77+
assert type(errors[0].__cause__) is InvalidSchemaValue
78+
79+
80+
@pytest.mark.parametrize("openapi_version", OPENAPI_VERSIONS)
81+
def test_allow_empty_value_false_preserves_legacy_error(openapi_version):
82+
openapi = make_openapi(
83+
openapi_version, {"enum": ["Active", ""]}, allowEmptyValue=False
84+
)
85+
request = make_request()
86+
87+
with pytest.warns(DeprecationWarning, match="allowEmptyValue"):
88+
validation_errors = list(openapi.iter_request_errors(request))
89+
assert len(validation_errors) == 1
90+
assert type(validation_errors[0]) is ParameterValidationError
91+
assert type(validation_errors[0].__cause__) is EmptyQueryParameterValue
92+
93+
with pytest.warns(DeprecationWarning, match="allowEmptyValue"):
94+
result = openapi.unmarshal_request(request)
95+
errors = list(result.errors)
96+
assert len(errors) == 1
97+
assert type(errors[0]) is ParameterValidationError
98+
assert type(errors[0].__cause__) is EmptyQueryParameterValue
99+
100+
101+
@pytest.mark.parametrize("openapi_version", OPENAPI_VERSIONS)
102+
def test_missing_query_parameter_remains_omitted(openapi_version):
103+
openapi = make_openapi(openapi_version, {"type": "string"})
104+
request = make_request(None)
105+
106+
assert list(openapi.iter_request_errors(request)) == []
107+
result = openapi.unmarshal_request(request)
108+
assert result.errors == []
109+
assert result.parameters.query == {}

tests/integration/test_petstore.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@
1414
from openapi_core import validate_response
1515
from openapi_core.casting.schemas.exceptions import CastError
1616
from openapi_core.datatypes import Parameters
17-
from openapi_core.deserializing.styles.exceptions import (
18-
EmptyQueryParameterValue,
19-
)
2017
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
2118
from openapi_core.templating.paths.exceptions import ServerNotFound
2219
from openapi_core.templating.security.exceptions import SecurityNotFound
@@ -548,7 +545,7 @@ def test_get_pets_raises_missing_required_param(self, spec):
548545

549546
assert result.body is None
550547

551-
def test_get_pets_empty_value(self, spec):
548+
def test_get_pets_empty_value_allowed_by_schema(self, spec):
552549
host_url = "http://petstore.swagger.io/v1"
553550
path_pattern = "/v1/pets"
554551
query_params = {
@@ -571,13 +568,12 @@ def test_get_pets_empty_value(self, spec):
571568
DeprecationWarning,
572569
match="Use of allowEmptyValue property is deprecated",
573570
):
574-
with pytest.raises(ParameterValidationError) as exc_info:
575-
validate_request(
576-
request,
577-
spec=spec,
578-
cls=V30RequestParametersValidator,
579-
)
580-
assert type(exc_info.value.__cause__) is EmptyQueryParameterValue
571+
result = validate_request(
572+
request,
573+
spec=spec,
574+
cls=V30RequestParametersValidator,
575+
)
576+
assert result is None
581577

582578
result = unmarshal_request(
583579
request, spec=spec, cls=V30RequestBodyUnmarshaller

0 commit comments

Comments
 (0)