Skip to content

Commit 9c056fe

Browse files
committed
fix: parse single list items in form data
1 parent da72ca2 commit 9c056fe

File tree

2 files changed

+22
-5
lines changed

2 files changed

+22
-5
lines changed

aws_lambda_powertools/event_handler/middlewares/openapi_validation.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
_normalize_errors,
1616
_regenerate_error_with_loc,
1717
get_missing_field_error,
18+
is_sequence_field,
1819
)
1920
from aws_lambda_powertools.event_handler.openapi.dependant import is_scalar_field
2021
from aws_lambda_powertools.event_handler.openapi.encoders import jsonable_encoder
@@ -150,11 +151,10 @@ def _parse_form_data(self, app: EventHandlerInstance) -> dict[str, Any]:
150151
"""Parse URL-encoded form data from the request body."""
151152
try:
152153
body = app.current_event.decoded_body or ""
153-
# parse_qs returns dict[str, list[str]], but we want dict[str, str] for single values
154+
# NOTE: Keep values as lists; we'll normalize per-field later based on the expected type.
155+
# This avoids breaking List[...] fields when only a single value is provided.
154156
parsed = parse_qs(body, keep_blank_values=True)
155-
156-
result: dict[str, Any] = {key: values[0] if len(values) == 1 else values for key, values in parsed.items()}
157-
return result
157+
return parsed
158158

159159
except Exception as e: # pragma: no cover
160160
raise RequestValidationError( # pragma: no cover
@@ -388,6 +388,10 @@ def _request_body_to_args(
388388
values[field.name] = deepcopy(field.default)
389389
continue
390390

391+
# Normalize lists for non-sequence fields
392+
if isinstance(value, list) and not is_sequence_field(field):
393+
value = value[0]
394+
391395
# MAINTENANCE: Handle byte and file fields
392396

393397
# Finally, validate the value

tests/functional/event_handler/_pydantic/test_openapi_validation_middleware.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1481,20 +1481,33 @@ def handler_custom_route_response_validation_error() -> Model:
14811481

14821482
def test_parse_form_data_url_encoded(gw_event):
14831483
"""Test _parse_form_data method with URL-encoded form data"""
1484-
1484+
# GIVEN an APIGatewayRestResolver with validation enabled
14851485
app = APIGatewayRestResolver(enable_validation=True)
14861486

14871487
@app.post("/form")
14881488
def post_form(name: Annotated[str, Form()], tags: Annotated[List[str], Form()]):
14891489
return {"name": name, "tags": tags}
14901490

1491+
# WHEN sending a POST request with URL-encoded form data
14911492
gw_event["httpMethod"] = "POST"
14921493
gw_event["path"] = "/form"
14931494
gw_event["headers"]["content-type"] = "application/x-www-form-urlencoded"
14941495
gw_event["body"] = "name=test&tags=tag1&tags=tag2"
14951496

14961497
result = app(gw_event, {})
1498+
1499+
# THEN it should parse the form data correctly
1500+
assert result["statusCode"] == 200
1501+
assert result["body"] == '{"name":"test","tags":["tag1","tag2"]}'
1502+
1503+
# WHEN sending a POST request with a single value for a list field
1504+
gw_event["body"] = "name=test&tags=tag1"
1505+
1506+
result = app(gw_event, {})
1507+
1508+
# THEN it should parse the form data correctly
14971509
assert result["statusCode"] == 200
1510+
assert result["body"] == '{"name":"test","tags":["tag1"]}'
14981511

14991512

15001513
def test_parse_form_data_wrong_value(gw_event):

0 commit comments

Comments
 (0)