Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
_normalize_errors,
_regenerate_error_with_loc,
get_missing_field_error,
is_sequence_field,
)
from aws_lambda_powertools.event_handler.openapi.dependant import is_scalar_field
from aws_lambda_powertools.event_handler.openapi.encoders import jsonable_encoder
Expand Down Expand Up @@ -150,11 +151,10 @@ def _parse_form_data(self, app: EventHandlerInstance) -> dict[str, Any]:
"""Parse URL-encoded form data from the request body."""
try:
body = app.current_event.decoded_body or ""
# parse_qs returns dict[str, list[str]], but we want dict[str, str] for single values
# NOTE: Keep values as lists; we'll normalize per-field later based on the expected type.
# This avoids breaking List[...] fields when only a single value is provided.
parsed = parse_qs(body, keep_blank_values=True)

result: dict[str, Any] = {key: values[0] if len(values) == 1 else values for key, values in parsed.items()}
return result
return parsed

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

# Normalize lists for non-sequence fields
if isinstance(value, list) and not is_sequence_field(field):
value = value[0]

# MAINTENANCE: Handle byte and file fields

# Finally, validate the value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1481,20 +1481,33 @@ def handler_custom_route_response_validation_error() -> Model:

def test_parse_form_data_url_encoded(gw_event):
"""Test _parse_form_data method with URL-encoded form data"""

# GIVEN an APIGatewayRestResolver with validation enabled
app = APIGatewayRestResolver(enable_validation=True)

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

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

result = app(gw_event, {})

# THEN it should parse the form data correctly
assert result["statusCode"] == 200
assert result["body"] == '{"name":"test","tags":["tag1","tag2"]}'

# WHEN sending a POST request with a single value for a list field
gw_event["body"] = "name=test&tags=tag1"

result = app(gw_event, {})

# THEN it should parse the form data correctly
assert result["statusCode"] == 200
assert result["body"] == '{"name":"test","tags":["tag1"]}'


def test_parse_form_data_wrong_value(gw_event):
Expand Down