This commit is contained in:
2026-03-17 18:32:44 +03:00
commit efcd4a8dfd
209 changed files with 33355 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
from datetime import datetime, timezone
from typing import Any
import uuid
import logging
from fastapi import Request, status
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from starlette.exceptions import HTTPException
logger = logging.getLogger(__name__)
def now_iso() -> str:
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
trace_id = getattr(request.state, "traceId", str(uuid.uuid4()))
is_json_error = any(e.get("type") in ("json_invalid", "json_decode", "value_error.jsondecode") for e in exc.errors())
if is_json_error:
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={
"code": "BAD_REQUEST",
"message": "Невалидный JSON",
"traceId": trace_id,
"timestamp": now_iso(),
"path": request.url.path,
"details": {"hint": "Проверьте запятые/кавычки"},
},
)
field_errors: list[dict[str, Any]] = []
for err in exc.errors():
loc = [str(x) for x in err.get("loc", []) if x != "body"]
field_name = ".".join(loc) if loc else "unknown"
msg = err.get("msg", "invalid")
if msg.startswith("Value error, "):
msg = msg.replace("Value error, ", "")
field_errors.append({
"field": field_name,
"issue": msg,
"rejectedValue": err.get("input", None),
})
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content={
"code": "VALIDATION_FAILED",
"message": "Некоторые поля не прошли валидацию",
"traceId": trace_id,
"timestamp": now_iso(),
"path": request.url.path,
"fieldErrors": field_errors,
},
)
async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse:
trace_id = getattr(request.state, "traceId", str(uuid.uuid4()))
message = str(exc.detail)
details = None
if isinstance(exc.detail, dict):
message = exc.detail.get("message", str(exc.detail))
details_data = {k: v for k, v in exc.detail.items() if k != "message"}
if details_data:
details = details_data
code = "HTTP_ERROR"
if exc.status_code == status.HTTP_409_CONFLICT:
code = "EMAIL_ALREADY_EXISTS" if "email" in message.lower() else "CONFLICT"
elif exc.status_code == status.HTTP_400_BAD_REQUEST:
code = "BAD_REQUEST"
elif exc.status_code == status.HTTP_401_UNAUTHORIZED:
code = "UNAUTHORIZED"
elif exc.status_code == status.HTTP_423_LOCKED:
code = "USER_INACTIVE"
elif exc.status_code == status.HTTP_403_FORBIDDEN:
code = "FORBIDDEN"
elif exc.status_code == status.HTTP_404_NOT_FOUND:
code = "NOT_FOUND"
if message == "Not Found":
message = "Ресурс не найден"
elif exc.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY:
code = "VALIDATION_FAILED"
content = {
"code": code,
"message": message,
"traceId": trace_id,
"timestamp": now_iso(),
"path": request.url.path,
}
if details:
content["details"] = details
return JSONResponse(
status_code=exc.status_code,
content=content,
)
async def unhandled_exception_handler(request: Request, exc: Exception) -> JSONResponse:
trace_id = getattr(request.state, "traceId", str(uuid.uuid4()))
logger.exception("Unhandled exception on %s", request.url.path, exc_info=exc)
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={
"code": "INTERNAL_ERROR",
"message": "Внутренняя ошибка сервера",
"traceId": trace_id,
"timestamp": now_iso(),
"path": request.url.path,
},
)