Feature request: add exception handlers return types to OpenAPI responses
Use case
I use an API Gateway event handler with validation enabled and I'd like exception handlers response types to be automatically add to the openAPI schema. I can add responses parameter to my endpoint definition, however it's not ideal as it adds a lot off repetition, duplication and introduce potential for incorrect openAPI schemas.
Solution/User Experience
import requests
from typing import List, Optional
from pydantic import BaseModel, Field
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
app = APIGatewayRestResolver(enable_validation=True)
class Todo(BaseModel):
user_id: int = Field(alias="userId")
id: int
title: str
completed: bool
class Error(BaseModel):
error: str
detail: Optional[list[dict]]
@app.exception_handler(Exception)
def internal_server_error(error: Exception) -> Error:
return Error(error="internal_server_error")
@app.get("/todos")
def get_todos() -> List[Todo]:
todo = requests.get("https://jsonplaceholder.typicode.com/todos")
todo.raise_for_status()
return todo.json()
def lambda_handler(event: dict, context: LambdaContext) -> dict:
return app.resolve(event, context)
This is what currently gets generated:
openapi: 3.0.3
info:
title: Powertools API
version: 1.0.0
servers:
- url: /
paths:
/todos:
get:
operationId: get_todos_get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
items:
$ref: '#/components/schemas/Todo'
type: array
title: Return
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
components:
schemas:
HTTPValidationError:
properties:
detail:
items:
$ref: '#/components/schemas/ValidationError'
type: array
title: Detail
type: object
title: HTTPValidationError
Todo:
properties:
userId:
type: integer
title: Userid
id:
type: integer
title: Id
title:
type: string
title: Title
completed:
type: boolean
title: Completed
type: object
required:
- userId
- id
- title
- completed
title: Todo
ValidationError:
properties:
loc:
items:
anyOf:
- type: string
- type: integer
type: array
title: Location
type:
type: string
title: Error Type
type: object
required:
- loc
- msg
- type
title: ValidationError
ideally this would be genrated:
openapi: 3.0.3
info:
title: Powertools API
version: 1.0.0
servers:
- url: /
paths:
/todos:
get:
operationId: get_todos_get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
items:
$ref: '#/components/schemas/Todo'
type: array
title: Return
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
HTTPValidationError:
properties:
detail:
items:
$ref: '#/components/schemas/ValidationError'
type: array
title: Detail
type: object
title: HTTPValidationError
Todo:
properties:
userId:
type: integer
title: Userid
id:
type: integer
title: Id
title:
type: string
title: Title
completed:
type: boolean
title: Completed
type: object
required:
- userId
- id
- title
- completed
title: Todo
ValidationError:
properties:
loc:
items:
anyOf:
- type: string
- type: integer
type: array
title: Location
type:
type: string
title: Error Type
type: object
required:
- loc
- msg
- type
title: ValidationError
Error:
properties:
error:
type: string
title: Error
detail:
type: array
items:
type: object
title: Detail
type: object
required:
- error
This would require a status code to be attached to each exception_handler and potentially some other context.
Alternative solutions
No response
Acknowledgment
- [X] This feature request meets Powertools for AWS Lambda (Python) Tenets
- [ ] Should this be considered in other Powertools for AWS Lambda languages? i.e. Java, TypeScript, and .NET
Thanks for opening your first issue here! We'll come back to you as soon as we can. In the meantime, check out the #python channel on our Powertools for AWS Lambda Discord: Invite link
Hi @ThomasLeedsLRH! Thanks for opening this issue and bringing this suggestion.
I don't think we should include exception_handler in the OpenAPI schema. The exception_handler feature is intended to catch general/custom Python exceptions, not define the responses in the OpenAPI schema. I realize there may be some overlap between these features, and the code may get repetitive if you define both, but if we assume we always should include exception_handler in the OpenAPI schema, we may be defining unwanted behavior for some routes that want to treat exceptions in a different way.
I will leave this issue open to hear more from you and other customers.
Hi @leandrodamascena, my issue is quite similar to this one, so I decided to comment here instead of opening a new issue.
The problem is that the "Validation Error" response is always included in the OpenAPI schema (primarily due to this code), which can lead to incorrect documentation generation when using @app.exception_handler(RequestValidationError).
In my case, I want to return validation errors with a 400 status code and in a different format, like this:
{
"error": {
"statusCode": 400,
"message": "Invalid request parameters",
"description": {
"body.user_email": "Field required"
}
}
}
To achieve this, I am using a custom exception handler and a custom class:
def validation_error_description(exc: RequestValidationError):
"""
Extracts and formats validation error messages from a RequestValidationError.
It creates a dictionary where each key represents the location of the validation error
in the request (e.g., "body.email"), and the corresponding value is the error message.
Args:
exc (RequestValidationError): The exception raised during request validation.
Returns:
dict: A dictionary containing detailed descriptions of each validation error.
"""
error_description = {}
for error in exc.errors():
# Creating a string representation of the location (path) of each error in the request
field = ".".join([str(elem) for elem in error["loc"]])
# Mapping the error location to its message
error_description[field] = error["msg"]
return error_description
class ExceptionHandlers:
"""
A class to handle common exceptions for AWS Lambda functions using AWS Lambda Powertools.
Attributes:
app (LambdaPowertoolsApp): An instance of the LambdaPowertoolsApp.
logger (Logger): An instance of the Powertools Logger.
"""
def __init__(self, app, logger=None):
self.app = app
self.logger = logger or Logger()
# 400 Bad Request
def invalid_params(self, exc):
"""
Handles RequestValidationError exceptions
by logging the error and returning a custom Response.
Args:
exc (RequestValidationError): The exception object.
Returns:
Response: A custom response with a status code of 400, indicating a bad request.
"""
if exc.__class__.__name__ == "TypeError" and "JSON object must be" in str(exc):
error_description = {"body": "Invalid or empty request body"}
else:
if isinstance(exc, RequestValidationError):
error_description = validation_error_description(exc)
else:
error_description = str(exc)
self.logger.error(
f"Data validation error: {error_description}",
extra={
"path": self.app.current_event.path,
"query_strings": self.app.current_event.query_string_parameters,
},
)
return Response(
status_code=HTTPStatus.BAD_REQUEST.value,
content_type=content_types.APPLICATION_JSON,
body={
"error": {
"statusCode": HTTPStatus.BAD_REQUEST.value,
"message": "Invalid request parameters",
"description": error_description,
}
},
)
...
exception_handlers = ExceptionHandlers(app=app)
...
@app.exception_handler([RequestValidationError, ValidationError, ValidationException])
def handle_invalid_params_wrapper(exc):
return exception_handlers.invalid_params(exc)
However, when I generate the OpenAPI schema, I have to manually add the 400 response to each of my routers:
@router.get(
"/user/<id>",
summary="Get user data",
responses={
200: {...},
400: {
"description": "Bad request",
"content": {"application/json": {"model": BadRequestResponse}},
},
...
But I also end up with a 422 response in my OpenAPI schema, which shouldn't be there:
Ideally, I would like to be able to reuse response models from exception_handler without having to define them for each router.
At the very least, the 422 response should not be added to the OpenAPI, as it can be overridden.
Hi @Hatter1337! Sorry I completely missed this comment! I'm adding it to my backlog to respond to this week, okay?
Hi @Hatter1337! Sorry I completely missed this comment! I'm adding it to my backlog to respond to this week, okay?
Sure, thank you
Hi @leandrodamascena, sorry for chasing up but is there any update on this?
Hi everyone! I'm adding this issue to investigate again in 3 weeks and have a final decision on how we can solve it!
I was reading in detail and we have 2 situations here:
1 - Customers that don't want to return 422 by default when a response doesn't match the function return.
2 - The ability to automatically add exceptions in the OpenAPI schema. I did some testing and it's not a breaking change if we add this as a flag in the resolver.
Thank you for your patience and I hope to have news soon.