119 lines
2.9 KiB
Python
119 lines
2.9 KiB
Python
|
|
"""客户端错误类型定义。
|
|||
|
|
|
|||
|
|
定义 ``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
|