Skip to content

Commit 9567549

Browse files
fix: add middleware validation per route
1 parent b2cf5db commit 9567549

File tree

2 files changed

+45
-7
lines changed

2 files changed

+45
-7
lines changed

aws_lambda_powertools/event_handler/api_gateway.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,19 @@ def _build_middleware_stack(self, router_middlewares: list[Callable[..., Any]],
546546
self.enable_validation if self.enable_validation is not None else app._enable_validation
547547
)
548548

549+
# If route needs validation but resolver didn't create the middlewares, create them now
550+
if route_validation_enabled and not hasattr(app, "_request_validation_middleware"):
551+
from aws_lambda_powertools.event_handler.middlewares.openapi_validation import (
552+
OpenAPIRequestValidationMiddleware,
553+
OpenAPIResponseValidationMiddleware,
554+
)
555+
556+
app._request_validation_middleware = OpenAPIRequestValidationMiddleware()
557+
app._response_validation_middleware = OpenAPIResponseValidationMiddleware(
558+
validation_serializer=app._serializer,
559+
has_response_validation_error=app._has_response_validation_error,
560+
)
561+
549562
# Add request validation middleware first if validation is enabled
550563
if route_validation_enabled and hasattr(app, "_request_validation_middleware"):
551564
all_middlewares.append(app._request_validation_middleware)

tests/functional/event_handler/_pydantic/test_per_route_validation.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,8 @@ def invalid_response() -> TodoItem:
240240

241241

242242
def test_per_route_validation_with_pydantic_v2():
243-
"""Test that per-route validation works correctly with Pydantic v2 models"""
244-
# GIVEN APIGatewayRestResolver with mixed validation
243+
"""Test that per-route validation actually validates when resolver has validation disabled"""
244+
# GIVEN APIGatewayRestResolver WITHOUT global validation
245245
app = APIGatewayRestResolver()
246246

247247
class Task(BaseModel):
@@ -250,7 +250,8 @@ class Task(BaseModel):
250250

251251
@app.get("/task", enable_validation=True)
252252
def get_task() -> Task:
253-
return Task(title="Important", priority=1)
253+
# Return invalid data — missing 'title' and 'priority'
254+
return cast(Task, {"wrong": "data"})
254255

255256
@app.get("/unvalidated-task")
256257
def get_unvalidated_task():
@@ -259,13 +260,12 @@ def get_unvalidated_task():
259260
event = load_event("apiGatewayProxyEvent.json")
260261
event["httpMethod"] = "GET"
261262

262-
# WHEN calling validated route
263+
# WHEN calling validated route with invalid data
263264
event["path"] = "/task"
264265
result = app(event, {})
265266

266-
# THEN should validate and serialize correctly
267-
assert result["statusCode"] == 200
268-
assert "Important" in result["body"]
267+
# THEN validation must reject it with 422
268+
assert result["statusCode"] == 422
269269

270270
# WHEN calling unvalidated route
271271
event["path"] = "/unvalidated-task"
@@ -274,3 +274,28 @@ def get_unvalidated_task():
274274
# THEN should return as-is without validation
275275
assert result["statusCode"] == 200
276276
assert "extra" in result["body"]
277+
278+
279+
def test_per_route_opt_in_validation_with_valid_data():
280+
"""Test that per-route opt-in validation passes valid data and serializes correctly"""
281+
# GIVEN APIGatewayRestResolver WITHOUT global validation
282+
app = APIGatewayRestResolver()
283+
284+
class Task(BaseModel):
285+
title: str
286+
priority: int
287+
288+
@app.get("/task", enable_validation=True)
289+
def get_task() -> Task:
290+
return Task(title="Important", priority=1)
291+
292+
event = load_event("apiGatewayProxyEvent.json")
293+
event["httpMethod"] = "GET"
294+
event["path"] = "/task"
295+
296+
# WHEN calling validated route with valid data
297+
result = app(event, {})
298+
299+
# THEN validation passes and response is serialized
300+
assert result["statusCode"] == 200
301+
assert "Important" in result["body"]

0 commit comments

Comments
 (0)