Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion openapi_core/validation/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ def _get_simple_param_or_header(
"Use of allowEmptyValue property is deprecated",
DeprecationWarning,
)
if allow_empty_values is None or not allow_empty_values:
if allow_empty_values is False:
# if "in" not defined then it's a Header
location_name = (param_or_header / "in").read_str("header")
if (
Expand Down
109 changes: 109 additions & 0 deletions tests/integration/test_empty_query_parameter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from typing import Optional

import pytest

from openapi_core import OpenAPI
from openapi_core.deserializing.styles.exceptions import (
EmptyQueryParameterValue,
)
from openapi_core.testing import MockRequest
from openapi_core.validation.request.exceptions import InvalidParameter
from openapi_core.validation.request.exceptions import ParameterValidationError
from openapi_core.validation.schemas.exceptions import InvalidSchemaValue

OPENAPI_VERSIONS = ["3.0.4", "3.1.1", "3.2.0"]


def make_openapi(openapi_version, schema, **parameter_options):
return OpenAPI.from_dict(
{
"openapi": openapi_version,
"info": {"title": "Empty query parameter", "version": "1.0.0"},
"paths": {
"/api": {
"get": {
"parameters": [
{
"name": "status",
"in": "query",
"schema": schema,
**parameter_options,
}
],
"responses": {"200": {"description": "OK"}},
}
}
},
}
)


def make_request(value: Optional[str] = ""):
args = {} if value is None else {"status": value}
return MockRequest("http://localhost", "get", "/api", args=args)


@pytest.mark.parametrize("openapi_version", OPENAPI_VERSIONS)
@pytest.mark.parametrize(
"schema", [{"enum": ["Active", ""]}, {"type": "string"}]
)
def test_empty_query_parameter_allowed_by_schema(openapi_version, schema):
openapi = make_openapi(openapi_version, schema)
request = make_request()

assert list(openapi.iter_request_errors(request)) == []
result = openapi.unmarshal_request(request)
assert result.errors == []
assert result.parameters.query == {"status": ""}


@pytest.mark.parametrize("openapi_version", OPENAPI_VERSIONS)
@pytest.mark.parametrize(
"schema", [{"enum": ["Active"]}, {"type": "string", "minLength": 1}]
)
def test_empty_query_parameter_rejected_by_schema(openapi_version, schema):
openapi = make_openapi(openapi_version, schema)
request = make_request()

validation_errors = list(openapi.iter_request_errors(request))
assert len(validation_errors) == 1
assert type(validation_errors[0]) is InvalidParameter
assert type(validation_errors[0].__cause__) is InvalidSchemaValue

result = openapi.unmarshal_request(request)
errors = list(result.errors)
assert len(errors) == 1
assert type(errors[0]) is InvalidParameter
assert type(errors[0].__cause__) is InvalidSchemaValue


@pytest.mark.parametrize("openapi_version", OPENAPI_VERSIONS)
def test_allow_empty_value_false_preserves_legacy_error(openapi_version):
openapi = make_openapi(
openapi_version, {"enum": ["Active", ""]}, allowEmptyValue=False
)
request = make_request()

with pytest.warns(DeprecationWarning, match="allowEmptyValue"):
validation_errors = list(openapi.iter_request_errors(request))
assert len(validation_errors) == 1
assert type(validation_errors[0]) is ParameterValidationError
assert type(validation_errors[0].__cause__) is EmptyQueryParameterValue

with pytest.warns(DeprecationWarning, match="allowEmptyValue"):
result = openapi.unmarshal_request(request)
errors = list(result.errors)
assert len(errors) == 1
assert type(errors[0]) is ParameterValidationError
assert type(errors[0].__cause__) is EmptyQueryParameterValue


@pytest.mark.parametrize("openapi_version", OPENAPI_VERSIONS)
def test_missing_query_parameter_remains_omitted(openapi_version):
openapi = make_openapi(openapi_version, {"type": "string"})
request = make_request(None)

assert list(openapi.iter_request_errors(request)) == []
result = openapi.unmarshal_request(request)
assert result.errors == []
assert result.parameters.query == {}
18 changes: 7 additions & 11 deletions tests/integration/test_petstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
from openapi_core import validate_response
from openapi_core.casting.schemas.exceptions import CastError
from openapi_core.datatypes import Parameters
from openapi_core.deserializing.styles.exceptions import (
EmptyQueryParameterValue,
)
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
from openapi_core.templating.paths.exceptions import ServerNotFound
from openapi_core.templating.security.exceptions import SecurityNotFound
Expand Down Expand Up @@ -548,7 +545,7 @@ def test_get_pets_raises_missing_required_param(self, spec):

assert result.body is None

def test_get_pets_empty_value(self, spec):
def test_get_pets_empty_value_allowed_by_schema(self, spec):
host_url = "http://petstore.swagger.io/v1"
path_pattern = "/v1/pets"
query_params = {
Expand All @@ -571,13 +568,12 @@ def test_get_pets_empty_value(self, spec):
DeprecationWarning,
match="Use of allowEmptyValue property is deprecated",
):
with pytest.raises(ParameterValidationError) as exc_info:
validate_request(
request,
spec=spec,
cls=V30RequestParametersValidator,
)
assert type(exc_info.value.__cause__) is EmptyQueryParameterValue
result = validate_request(
request,
spec=spec,
cls=V30RequestParametersValidator,
)
assert result is None

result = unmarshal_request(
request, spec=spec, cls=V30RequestBodyUnmarshaller
Expand Down