v0.1 - макет интерфейса, аутентификация через логин, аккаунт админа, создание партии в 2 этапа, базовые настройки профиля и группы, переключение между группами, статистика
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
"""Доменные исключения и единый конверт ошибок API.
|
||||
|
||||
Формат ответа: {"error": {"code": "...", "message": "...", "details": {...}}}
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import Request
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
|
||||
class AppError(Exception):
|
||||
"""Базовая ошибка приложения со стабильным машинным кодом."""
|
||||
|
||||
status_code: int = 400
|
||||
code: str = "BAD_REQUEST"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
*,
|
||||
code: str | None = None,
|
||||
status_code: int | None = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
if code is not None:
|
||||
self.code = code
|
||||
if status_code is not None:
|
||||
self.status_code = status_code
|
||||
self.details = details
|
||||
|
||||
def to_response(self) -> JSONResponse:
|
||||
return JSONResponse(
|
||||
status_code=self.status_code,
|
||||
content={
|
||||
"error": {
|
||||
"code": self.code,
|
||||
"message": self.message,
|
||||
"details": self.details,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class NotFoundError(AppError):
|
||||
status_code = 404
|
||||
code = "NOT_FOUND"
|
||||
|
||||
|
||||
class ForbiddenError(AppError):
|
||||
status_code = 403
|
||||
code = "FORBIDDEN"
|
||||
|
||||
|
||||
class ValidationError(AppError):
|
||||
status_code = 422
|
||||
code = "VALIDATION_ERROR"
|
||||
|
||||
|
||||
class ConflictError(AppError):
|
||||
status_code = 409
|
||||
code = "CONFLICT"
|
||||
|
||||
|
||||
class AuthError(AppError):
|
||||
status_code = 401
|
||||
code = "UNAUTHORIZED"
|
||||
|
||||
|
||||
# ─── Конкретные ошибки со стабильными кодами для фронта ───────────────────────
|
||||
|
||||
class NoGroupError(AppError):
|
||||
status_code = 409
|
||||
code = "NO_GROUP"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__("Вы не состоите ни в одной группе.")
|
||||
|
||||
|
||||
class NotGroupMemberError(ForbiddenError):
|
||||
code = "NOT_GROUP_MEMBER"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__("Вы не являетесь участником этой группы.")
|
||||
|
||||
|
||||
class NicknameTakenError(ConflictError):
|
||||
code = "NICKNAME_TAKEN"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__("Никнейм уже занят.")
|
||||
|
||||
|
||||
class FactionNotAvailableError(ValidationError):
|
||||
code = "FACTION_NOT_AVAILABLE"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__("Фракция недоступна выбранной группе.")
|
||||
|
||||
|
||||
class DuplicateParticipantError(ValidationError):
|
||||
code = "DUPLICATE_PARTICIPANT"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__("Игрок или фракция повторяются в партии.")
|
||||
|
||||
|
||||
class InvalidRankingError(ValidationError):
|
||||
code = "INVALID_RANKING"
|
||||
|
||||
def __init__(self, message: str = "Некорректная расстановка мест.") -> None:
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class InvalidCredentialsError(AuthError):
|
||||
code = "INVALID_CREDENTIALS"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__("Неверный логин или пароль.")
|
||||
|
||||
|
||||
async def app_error_handler(_request: Request, exc: AppError) -> JSONResponse:
|
||||
return exc.to_response()
|
||||
Reference in New Issue
Block a user