Skip to content

Commit 53678cf

Browse files
fix(event_handler): fix ALB resolver returns when response body is None (#8194)
* fix(event_handler): handle ALB response when it's None * fix(event_handler): handle ALB response when it's None
1 parent 5b8ba36 commit 53678cf

3 files changed

Lines changed: 42 additions & 11 deletions

File tree

aws_lambda_powertools/event_handler/api_gateway.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3343,22 +3343,13 @@ def _get_base_path(self) -> str:
33433343
# ALB doesn't have a stage variable, so we just return an empty string
33443344
return ""
33453345

3346-
# BedrockResponse is not used here but adding the same signature to keep strong typing
33473346
@override
33483347
def _to_response(self, result: dict | tuple | Response | BedrockResponse) -> Response | BedrockResponse:
33493348
"""Convert the route's result to a Response
33503349
33513350
ALB requires a non-null body otherwise it converts as HTTP 5xx
3352-
3353-
3 main result types are supported:
3354-
3355-
- Dict[str, Any]: Rest api response with just the Dict to json stringify and content-type is set to
3356-
application/json
3357-
- Tuple[dict, int]: Same dict handling as above but with the option of including a status code
3358-
- Response: returned as is, and allows for more flexibility
33593351
"""
3360-
3361-
# NOTE: Minor override for early return on Response with null body for ALB
3352+
# ALB doesn't support null body - convert before building the final response
33623353
if isinstance(result, Response) and result.body is None:
33633354
logger.debug("ALB doesn't allow None responses; converting to empty string")
33643355
result.body = ""

aws_lambda_powertools/event_handler/middlewares/openapi_validation.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,9 +296,13 @@ def _handle_response(self, *, route: Route, response: Response):
296296
# JSON serialize the body without validation
297297
response.body = jsonable_encoder(response.body, custom_serializer=self._validation_serializer)
298298
else:
299+
# ALB resolver converts None body to "" to prevent ALB 5xx errors,
300+
# but the validation should still see it as None.
301+
response_content = None if response.body == "" and field.type_ in (None, type(None)) else response.body
302+
299303
response.body = self._serialize_response_with_validation(
300304
field=field,
301-
response_content=response.body,
305+
response_content=response_content,
302306
has_route_custom_response_validation=route.custom_response_validation_http_code is not None,
303307
)
304308

tests/functional/event_handler/_pydantic/test_openapi_validation_middleware.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4227,3 +4227,39 @@ def handler(session_id: Annotated[str, Cookie()]):
42274227
assert result["statusCode"] == 200
42284228
body = json.loads(result["body"])
42294229
assert body["session_id"] == "lattice_v1_abc"
4230+
4231+
4232+
def test_alb_response_none_body_with_validation(gw_event_alb):
4233+
# GIVEN an ALBResolver with validation enabled
4234+
app = ALBResolver(enable_validation=True)
4235+
4236+
gw_event_alb["path"] = "/no-content"
4237+
gw_event_alb["httpMethod"] = "DELETE"
4238+
4239+
# WHEN a handler returns Response with body=None and return type is None
4240+
@app.delete("/no-content")
4241+
def handler() -> None:
4242+
return Response(status_code=204, body=None)
4243+
4244+
# THEN the response should be 204 with empty body (not 422 validation error)
4245+
result = app(gw_event_alb, {})
4246+
assert result["statusCode"] == 204
4247+
assert result["body"] == ""
4248+
4249+
4250+
def test_alb_response_typed_none_body_with_validation(gw_event_alb):
4251+
# GIVEN an ALBResolver with validation enabled
4252+
app = ALBResolver(enable_validation=True)
4253+
4254+
gw_event_alb["path"] = "/no-content"
4255+
gw_event_alb["httpMethod"] = "DELETE"
4256+
4257+
# WHEN a handler returns Response[None] with body=None
4258+
@app.delete("/no-content")
4259+
def handler() -> Response[None]:
4260+
return Response(status_code=204, body=None)
4261+
4262+
# THEN the response should be 204 with empty body (not 422 validation error)
4263+
result = app(gw_event_alb, {})
4264+
assert result["statusCode"] == 204
4265+
assert result["body"] == ""

0 commit comments

Comments
 (0)