from fastapi import FastAPI, Request from fastapi.exceptions import RequestValidationError from fastapi.responses import ORJSONResponse from starlette.exceptions import HTTPException as StarletteHTTPException class AppError(Exception): def __init__(self, message: str, status_code: int = 400, code: str = "app_error") -> None: self.message = message self.status_code = status_code self.code = code class NotFoundError(AppError): def __init__(self, message: str = "Resource not found") -> None: super().__init__(message=message, status_code=404, code="not_found") class LLMUnavailableError(AppError): def __init__(self, message: str = "LLM service is unavailable") -> None: super().__init__(message=message, status_code=503, code="llm_unavailable") class ParseError(AppError): def __init__(self, message: str = "Unable to parse AI output") -> None: super().__init__(message=message, status_code=422, code="parse_error") def error_payload(code: str, message: str, details: object | None = None) -> dict[str, object]: payload: dict[str, object] = {"error": {"code": code, "message": message}} if details is not None: payload["error"]["details"] = details return payload def register_exception_handlers(app: FastAPI) -> None: @app.exception_handler(AppError) async def app_error_handler(_: Request, exc: AppError) -> ORJSONResponse: return ORJSONResponse( status_code=exc.status_code, content=error_payload(exc.code, exc.message), ) @app.exception_handler(StarletteHTTPException) async def http_error_handler(_: Request, exc: StarletteHTTPException) -> ORJSONResponse: return ORJSONResponse( status_code=exc.status_code, content=error_payload("http_error", str(exc.detail)), ) @app.exception_handler(RequestValidationError) async def validation_error_handler(_: Request, exc: RequestValidationError) -> ORJSONResponse: return ORJSONResponse( status_code=422, content=error_payload("validation_error", "Invalid request payload", exc.errors()), )