Files
monlet/server/monlet_server/errors.py

62 lines
2.1 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",
403: "forbidden",
409: "conflict",
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: ErrorCode = _STATUS_TO_CODE.get(exc.status_code, "internal")
if exc.status_code == 403:
if exc.detail == "agent blocked":
code = "agent_blocked"
elif exc.detail == "agent not accepted":
code = "agent_not_accepted"
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")