Skip to content

Commit 16bdeaf

Browse files
authored
fix(event_handler): always add 422 response to the schema (#3995)
1 parent 0082a67 commit 16bdeaf

File tree

4 files changed

+62
-35
lines changed

4 files changed

+62
-35
lines changed

aws_lambda_powertools/event_handler/api_gateway.py

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,18 @@ def _get_openapi_path(
503503
if request_body_oai:
504504
operation["requestBody"] = request_body_oai
505505

506+
# Validation failure response (422) will always be part of the schema
507+
operation_responses: Dict[int, OpenAPIResponse] = {
508+
422: {
509+
"description": "Validation Error",
510+
"content": {
511+
"application/json": {
512+
"schema": {"$ref": COMPONENT_REF_PREFIX + "HTTPValidationError"},
513+
},
514+
},
515+
},
516+
}
517+
506518
# Add the response to the OpenAPI operation
507519
if self.responses:
508520
for status_code in list(self.responses):
@@ -549,45 +561,34 @@ def _get_openapi_path(
549561

550562
response["content"][content_type] = new_payload
551563

552-
operation["responses"] = self.responses
564+
# Merge the user provided response with the default responses
565+
operation_responses[status_code] = response
553566
else:
554567
# Set the default 200 response
555-
responses = operation.setdefault("responses", {})
556-
success_response = responses.setdefault(200, {})
557-
success_response["description"] = self.response_description or _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION
558-
success_response["content"] = {"application/json": {"schema": {}}}
559-
json_response = success_response["content"].setdefault("application/json", {})
560-
561-
# Add the response schema to the OpenAPI 200 response
562-
json_response.update(
563-
self._openapi_operation_return(
564-
param=dependant.return_param,
565-
model_name_map=model_name_map,
566-
field_mapping=field_mapping,
567-
),
568+
response_schema = self._openapi_operation_return(
569+
param=dependant.return_param,
570+
model_name_map=model_name_map,
571+
field_mapping=field_mapping,
568572
)
569573

570-
# Add validation failure response (422)
571-
operation["responses"][422] = {
572-
"description": "Validation Error",
573-
"content": {
574-
"application/json": {
575-
"schema": {"$ref": COMPONENT_REF_PREFIX + "HTTPValidationError"},
576-
},
577-
},
574+
# Add the response schema to the OpenAPI 200 response
575+
operation_responses[200] = {
576+
"description": self.response_description or _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION,
577+
"content": {"application/json": response_schema},
578578
}
579579

580-
# Add the validation error schema to the definitions, but only if it hasn't been added yet
581-
if "ValidationError" not in definitions:
582-
definitions.update(
583-
{
584-
"ValidationError": validation_error_definition,
585-
"HTTPValidationError": validation_error_response_definition,
586-
},
587-
)
588-
580+
operation["responses"] = operation_responses
589581
path[self.method.lower()] = operation
590582

583+
# Add the validation error schema to the definitions, but only if it hasn't been added yet
584+
if "ValidationError" not in definitions:
585+
definitions.update(
586+
{
587+
"ValidationError": validation_error_definition,
588+
"HTTPValidationError": validation_error_response_definition,
589+
},
590+
)
591+
591592
# Generate the response schema
592593
return path, definitions
593594

aws_lambda_powertools/event_handler/openapi/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"type": "array",
2929
"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
3030
},
31-
"msg": {"title": "Message", "type": "string"},
31+
# For security reasons, we hide **msg** details (don't leak Python, Pydantic or filenames)
3232
"type": {"title": "Error Type", "type": "string"},
3333
},
3434
"required": ["loc", "msg", "type"],

tests/functional/event_handler/test_openapi_params.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def handler():
5252

5353
assert JSON_CONTENT_TYPE in response.content
5454
json_response = response.content[JSON_CONTENT_TYPE]
55-
assert json_response.schema_ == Schema()
55+
assert json_response.schema_ is None
5656
assert not json_response.examples
5757
assert not json_response.encoding
5858

tests/functional/event_handler/test_openapi_responses.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,34 @@ def handler():
5050
assert 202 in responses.keys()
5151
assert responses[202].description == "Custom response"
5252

53-
assert 200 not in responses.keys()
54-
assert 422 not in responses.keys()
53+
assert 200 not in responses.keys() # 200 was not added due to custom responses
54+
assert 422 in responses.keys() # 422 is always added due to potential data validation errors
55+
56+
57+
def test_openapi_422_default_response():
58+
app = APIGatewayRestResolver(enable_validation=True)
59+
60+
@app.get("/")
61+
def handler():
62+
return {"message": "hello world"}
63+
64+
schema = app.get_openapi_schema()
65+
responses = schema.paths["/"].get.responses
66+
assert 422 in responses.keys()
67+
assert responses[422].description == "Validation Error"
68+
69+
70+
def test_openapi_422_custom_response():
71+
app = APIGatewayRestResolver(enable_validation=True)
72+
73+
@app.get("/", responses={422: {"description": "Custom validation response"}})
74+
def handler():
75+
return {"message": "hello world"}
76+
77+
schema = app.get_openapi_schema()
78+
responses = schema.paths["/"].get.responses
79+
assert 422 in responses.keys()
80+
assert responses[422].description == "Custom validation response"
5581

5682

5783
def test_openapi_200_custom_schema():

0 commit comments

Comments
 (0)