from __future__ import annotations from typing import Any from fastapi import FastAPI, Request from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse from starlette.exceptions import HTTPException as StarletteHTTPException class APIError(Exception): def __init__( self, status_code: int, code: str, message: str, detail: Any | None = None, ) -> None: super().__init__(message) self.status_code = status_code self.code = code self.message = message self.detail = detail def error_payload(code: str, message: str, detail: Any | None = None) -> dict[str, Any]: payload: dict[str, Any] = {"code": code, "message": message} if detail is not None: payload["detail"] = detail return payload def install_exception_handlers(app: FastAPI) -> None: @app.exception_handler(APIError) async def api_error_handler(_: Request, exc: APIError) -> JSONResponse: return JSONResponse( status_code=exc.status_code, content=error_payload(exc.code, exc.message, exc.detail), ) @app.exception_handler(RequestValidationError) async def validation_error_handler( _: Request, exc: RequestValidationError ) -> JSONResponse: return JSONResponse( status_code=422, content=error_payload("validation_error", "请求参数校验失败", exc.errors()), ) @app.exception_handler(StarletteHTTPException) async def http_error_handler(_: Request, exc: StarletteHTTPException) -> JSONResponse: detail = exc.detail if isinstance(detail, dict) and "code" in detail: return JSONResponse(status_code=exc.status_code, content=detail) return JSONResponse( status_code=exc.status_code, content=error_payload("http_error", str(detail)), )