from __future__ import annotations import logging from fastapi import FastAPI, HTTPException, Request, status from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse from sqlalchemy.exc import SQLAlchemyError logger = logging.getLogger("app.errors") class AppError(Exception): def __init__(self, status_code: int, detail: str) -> None: self.status_code = status_code self.detail = detail super().__init__(detail) class BadRequestError(AppError): def __init__(self, detail: str = "Bad request") -> None: super().__init__(status.HTTP_400_BAD_REQUEST, detail) class UnauthorizedError(AppError): def __init__(self, detail: str = "Authentication failed") -> None: super().__init__(status.HTTP_401_UNAUTHORIZED, detail) class ForbiddenError(AppError): def __init__(self, detail: str = "Access denied") -> None: super().__init__(status.HTTP_403_FORBIDDEN, detail) class NotFoundError(AppError): def __init__(self, detail: str = "Resource not found") -> None: super().__init__(status.HTTP_404_NOT_FOUND, detail) class ConflictError(AppError): def __init__(self, detail: str = "Conflict detected") -> None: super().__init__(status.HTTP_409_CONFLICT, detail) class TooManyRequestsError(AppError): def __init__(self, detail: str = "Too many requests") -> None: super().__init__(status.HTTP_429_TOO_MANY_REQUESTS, detail) def register_exception_handlers(app: FastAPI) -> None: @app.exception_handler(AppError) async def handle_app_error(_: Request, exc: AppError) -> JSONResponse: return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail}) @app.exception_handler(HTTPException) async def handle_http_exception(_: Request, exc: HTTPException) -> JSONResponse: return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail}) @app.exception_handler(RequestValidationError) async def handle_validation_error( _: Request, exc: RequestValidationError ) -> JSONResponse: return JSONResponse( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content={"detail": "Validation error"}, ) @app.exception_handler(SQLAlchemyError) async def handle_database_error(_: Request, exc: SQLAlchemyError) -> JSONResponse: logger.exception("Database operation failed") return JSONResponse( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content={"detail": "Internal server error"}, ) @app.exception_handler(Exception) async def handle_unexpected_error(_: Request, exc: Exception) -> JSONResponse: logger.exception("Unhandled application error") return JSONResponse( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content={"detail": "Internal server error"}, )