ForcePilot/backend/package/yuxi/channels/contract/errors/client.py

119 lines
2.9 KiB
Python
Raw Normal View History

"""客户端错误类型定义。
定义 ``ClientError`` 抽象基类及其 4 个具体子类表示由调用方引起的错误
4xx 语义参数校验失败认证失败资源不存在权限不足
"""
from __future__ import annotations
from typing import Any
from yuxi.channels.contract.errors.base import Error
class ClientError(Error):
"""客户端错误抽象基类。
表示由调用方引起的错误4xx 语义子类包括 ``ValidationError`` /
``AuthError`` / ``NotFoundError`` / ``PermissionDeniedError``
"""
error_code = "CLIENT_ERROR"
class ValidationError(ClientError, ValueError):
"""字段校验失败错误。
同时继承 ValueError 以兼容 Pydantic field_validator
"""
error_code = "VALIDATION_ERROR"
def __init__(
self,
field: str,
message: str,
*,
trace_id: str | None = None,
) -> None:
super().__init__(message, trace_id=trace_id)
self.field = field
def to_dict(self) -> dict[str, Any]:
data = super().to_dict()
data["field"] = self.field
return data
class AuthError(ClientError):
"""认证失败错误。
调用方身份认证失败时抛出 token 无效签名错误HTTP 401
"""
error_code = "AUTH_ERROR"
def __init__(
self,
reason: str,
*,
trace_id: str | None = None,
) -> None:
super().__init__(f"Authentication failed: {reason}", trace_id=trace_id)
self.reason = reason
def to_dict(self) -> dict[str, Any]:
data = super().to_dict()
data["reason"] = self.reason
return data
class NotFoundError(ClientError):
"""资源不存在错误。
调用方请求的资源不存在时抛出HTTP 404
"""
error_code = "NOT_FOUND"
def __init__(
self,
resource: str,
id: str,
*,
trace_id: str | None = None,
) -> None:
super().__init__(f"{resource} not found: {id}", trace_id=trace_id)
self.resource = resource
self.id = id
def to_dict(self) -> dict[str, Any]:
data = super().to_dict()
data["resource"] = self.resource
data["id"] = self.id
return data
class PermissionDeniedError(ClientError):
"""权限不足错误。
调用方无权限执行请求的操作时抛出HTTP 403命名为 ``PermissionDeniedError``
以避免与 Python 内置 ``PermissionError`` 冲突
"""
error_code = "PERMISSION_DENIED"
def __init__(
self,
reason: str,
*,
trace_id: str | None = None,
) -> None:
super().__init__(f"Permission denied: {reason}", trace_id=trace_id)
self.reason = reason
def to_dict(self) -> dict[str, Any]:
data = super().to_dict()
data["reason"] = self.reason
return data