83 lines
2.9 KiB
Python
83 lines
2.9 KiB
Python
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"},
|
|
)
|