76 lines
2.8 KiB
Python
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"))
|