Files
routesapi/app/core/exception_handlers.py

113 lines
3.4 KiB
Python

"""Professional exception handlers for the API."""
import logging
from fastapi import Request, status
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException
from app.core.exceptions import APIException
from app.models.errors import ErrorResponse, ErrorDetail
logger = logging.getLogger(__name__)
async def api_exception_handler(request: Request, exc: APIException) -> JSONResponse:
"""Handle custom API exceptions."""
request_id = getattr(request.state, "request_id", None)
error_response = ErrorResponse(
success=False,
error=ErrorDetail(
field=exc.field,
message=exc.message,
code=exc.code
),
path=request.url.path,
request_id=request_id
)
logger.warning(f"API Exception: {exc.code} - {exc.message} (Request ID: {request_id})")
return JSONResponse(
status_code=exc.status_code,
content=error_response.model_dump(exclude_none=True)
)
async def http_exception_handler(request: Request, exc: StarletteHTTPException) -> JSONResponse:
"""Handle HTTP exceptions."""
request_id = getattr(request.state, "request_id", None)
error_response = ErrorResponse(
success=False,
error=ErrorDetail(
message=exc.detail,
code="HTTP_ERROR"
),
path=request.url.path,
request_id=request_id
)
logger.warning(f"HTTP Exception: {exc.status_code} - {exc.detail} (Request ID: {request_id})")
return JSONResponse(
status_code=exc.status_code,
content=error_response.model_dump(exclude_none=True)
)
async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
"""Handle validation errors with detailed field information."""
request_id = getattr(request.state, "request_id", None)
errors = exc.errors()
if errors:
first_error = errors[0]
field = ".".join(str(loc) for loc in first_error.get("loc", []))
message = first_error.get("msg", "Validation error")
else:
field = None
message = "Validation error"
error_response = ErrorResponse(
success=False,
error=ErrorDetail(
field=field,
message=message,
code="VALIDATION_ERROR"
),
path=request.url.path,
request_id=request_id
)
logger.warning(f"Validation Error: {message} (Field: {field}, Request ID: {request_id})")
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content=error_response.model_dump(exclude_none=True)
)
async def general_exception_handler(request: Request, exc: Exception) -> JSONResponse:
"""Handle unexpected exceptions."""
request_id = getattr(request.state, "request_id", None)
error_response = ErrorResponse(
success=False,
error=ErrorDetail(
message="An unexpected error occurred. Please try again later.",
code="INTERNAL_SERVER_ERROR"
),
path=request.url.path,
request_id=request_id
)
logger.error(f"Unexpected Error: {str(exc)} (Request ID: {request_id})", exc_info=True)
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content=error_response.model_dump(exclude_none=True)
)