PySimpleSocial/util/exception_handlers.py

76 lines
2.8 KiB
Python

from config import LOGGER, NotAuthenticated
from fastapi import Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from fastapi.exceptions import HTTPException, StarletteHTTPException
from slowapi.errors import RateLimitExceeded
async def rate_limited(request: Request, error: RateLimitExceeded) -> JSONResponse:
"""
Handles the equivalent of a 429 Too Many Requests error
"""
n = 0
while True:
if error.detail[n].isnumeric():
n += 1
else:
break
error.detail = error.detail[:n] + " requests" + error.detail[n:]
LOGGER.info(f"{request.client.host} got rate-limited at {str(request.url)} " f"(exceeded {error.detail})")
return JSONResponse(
status_code=200,
content=dict(
status_code=429,
msg=f"Too many requests, retry after {error.detail[error.detail.find('per') + 4:]}",
),
)
def not_authenticated(request: Request, _: NotAuthenticated) -> JSONResponse:
"""
Handles the equivalent of a 401 Unauthorized exception
"""
LOGGER.info(f"{request.client.host} failed to authenticate at {str(request.url)}")
return JSONResponse(status_code=200, content=dict(status_code=401, msg="Authentication is required"))
def request_invalid(request: Request, exc: RequestValidationError) -> JSONResponse:
"""
Handles Bad Request exceptions from FastAPI
"""
LOGGER.info(f"{request.client.host} sent an invalid request at {request.url!r}: {type(exc).__name__}: {exc}")
return JSONResponse(
status_code=200,
content=dict(status_code=400, msg=f"Bad request: {type(exc).__name__}: {exc}"),
)
def http_exception(request: Request, exc: HTTPException | StarletteHTTPException) -> JSONResponse:
"""
Handles HTTP-specific exceptions raised explicitly by
path operations
"""
if exc.status_code >= 500:
LOGGER.error(
f"{request.client.host} raised a {exc.status_code} error at {request.url!r}:" f"{type(exc).__name__}: {exc}"
)
return JSONResponse(status_code=200, content=dict(status_code=500, msg="Internal Server Error"))
else:
LOGGER.info(f"{request.client.host} raised an HTTP error ({exc.status_code}) at {str(request.url)}")
return JSONResponse(status_code=200, content=dict(status_code=exc.status_code, msg=exc.detail))
async def generic_error(request: Request, exc: Exception) -> JSONResponse:
"""
Handles generic, unexpected errors in the ASGI application
"""
LOGGER.info(f"{request.client.host} raised an unexpected error ({type(exc).__name__}: {exc}) at {str(request.url)}")
# We can't leak anything about the error, it would be too risky
return JSONResponse(status_code=200, content=dict(status_code=500, msg="Internal Server Error"))