55 lines
1.9 KiB
Python
55 lines
1.9 KiB
Python
from fastapi import FastAPI, Request
|
|
from fastapi.exceptions import RequestValidationError
|
|
from fastapi.responses import JSONResponse
|
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
|
|
from .logging_config import get_logger, request_id_var
|
|
from .schemas import ErrorBody, ErrorCode, ErrorResponse
|
|
|
|
log = get_logger("monlet.errors")
|
|
|
|
|
|
def _request_id(request: Request) -> str:
|
|
rid = getattr(request.state, "request_id", None)
|
|
if rid is None:
|
|
rid = request_id_var.get() or ""
|
|
return rid
|
|
|
|
|
|
def error_response(
|
|
request: Request, status_code: int, code: ErrorCode, message: str
|
|
) -> JSONResponse:
|
|
body = ErrorResponse(
|
|
error=ErrorBody(code=code, message=message, request_id=_request_id(request))
|
|
)
|
|
return JSONResponse(status_code=status_code, content=body.model_dump())
|
|
|
|
|
|
_STATUS_TO_CODE: dict[int, ErrorCode] = {
|
|
400: "validation",
|
|
401: "unauthorized",
|
|
404: "not_found",
|
|
413: "payload_too_large",
|
|
429: "rate_limited",
|
|
500: "internal",
|
|
503: "not_ready",
|
|
}
|
|
|
|
|
|
def install_error_handlers(app: FastAPI) -> None:
|
|
@app.exception_handler(RequestValidationError)
|
|
async def _validation(request: Request, exc: RequestValidationError):
|
|
message = "request validation failed"
|
|
return error_response(request, 400, "validation", message)
|
|
|
|
@app.exception_handler(StarletteHTTPException)
|
|
async def _http(request: Request, exc: StarletteHTTPException):
|
|
code = _STATUS_TO_CODE.get(exc.status_code, "internal")
|
|
msg = exc.detail if isinstance(exc.detail, str) else code
|
|
return error_response(request, exc.status_code, code, msg)
|
|
|
|
@app.exception_handler(Exception)
|
|
async def _unhandled(request: Request, exc: Exception):
|
|
log.exception("unhandled_exception", error=type(exc).__name__)
|
|
return error_response(request, 500, "internal", "internal server error")
|