113 lines
3.4 KiB
Python
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)
|
|
)
|
|
|