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

236 lines
7.5 KiB
Python
Raw Normal View History

"""服务端错误类型定义。
定义 ``ServerError`` 抽象基类及其 4 个具体子类表示由服务端引起的错误
5xx 语义内部错误依赖故障超时未实现``ServerError`` 额外携带
``cause`` 字段记录原始错误便于追踪
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from yuxi.channels.contract.errors.base import Error
if TYPE_CHECKING:
from yuxi.channels.contract.errors.transport import TransportErrorCategory
class ServerError(Error):
"""服务端错误抽象基类。
表示由服务端引起的错误5xx 语义额外携带 ``cause`` 字段记录原始错误
便于跨链路追踪子类包括 ``InternalError`` / ``DependencyError`` /
``TimeoutError``
"""
error_code = "SERVER_ERROR"
def __init__(
self,
message: str,
*,
trace_id: str | None = None,
cause: Error | Exception | None = None,
category_hint: TransportErrorCategory | None = None,
) -> None:
super().__init__(message, trace_id=trace_id, category_hint=category_hint)
self.cause = cause
def to_dict(self) -> dict[str, Any]:
data = super().to_dict()
if self.cause is None:
data["cause"] = None
elif isinstance(self.cause, Error):
data["cause"] = self.cause.to_dict()
else:
# 原生 Exception 无 to_dict(),仅记录类型与信息以便追踪
data["cause"] = {
"type": type(self.cause).__name__,
"message": str(self.cause),
}
return data
class InternalError(ServerError):
"""内部错误。
服务端内部异常导致的错误HTTP 500``cause`` 可选当原始错误为
``Error`` 子类时传入以保留结构化错误链当原始错误为原生 ``Exception``
时可不传 ``cause``通过 ``message`` 携带异常信息
``category_hint`` 透传仅供 API 一致性内部错误默认 ``None``翻译器按
``isinstance`` 映射为 ``permanent``透传仅供显式覆盖默认分类
"""
error_code = "INTERNAL"
def __init__(
self,
cause: Error | Exception | None = None,
*,
trace_id: str | None = None,
message: str | None = None,
category_hint: TransportErrorCategory | None = None,
) -> None:
super().__init__(
message or "Internal error",
trace_id=trace_id,
cause=cause,
category_hint=category_hint,
)
class DependencyError(ServerError):
"""依赖故障错误。
外部依赖如数据库Redis第三方服务故障时抛出HTTP 502
"""
error_code = "DEPENDENCY"
def __init__(
self,
dep: str,
cause: Error | Exception,
*,
message: str | None = None,
trace_id: str | None = None,
category_hint: TransportErrorCategory | None = None,
) -> None:
super().__init__(
message or f"Dependency {dep} failed",
trace_id=trace_id,
cause=cause,
category_hint=category_hint,
)
self.dep = dep
def to_dict(self) -> dict[str, Any]:
data = super().to_dict()
data["dep"] = self.dep
return data
class OperationTimeoutError(ServerError):
"""操作超时错误。
操作超时时抛出HTTP 504error_code=``TIMEOUT``命名为
``OperationTimeoutError`` 以避免与 Python 内置 ``TimeoutError`` 冲突
遵循 ``PermissionDeniedError`` 的同一命名约定
"""
error_code = "TIMEOUT"
def __init__(
self,
timeout_ms: int,
*,
message: str | None = None,
trace_id: str | None = None,
category_hint: TransportErrorCategory | None = None,
) -> None:
"""初始化超时错误。
Args:
timeout_ms: 超时毫秒数写入 ``details`` 供客户端参考
message: 自定义错误信息 ``None`` 时使用默认模板
``"Operation timed out after {timeout_ms}ms"``
trace_id: 调用链路追踪 ID用于跨链路关联
category_hint: 传输分类建议供翻译器读取F-06
"""
super().__init__(
message or f"Operation timed out after {timeout_ms}ms",
trace_id=trace_id,
category_hint=category_hint,
)
self.timeout_ms = timeout_ms
def to_dict(self) -> dict[str, Any]:
data = super().to_dict()
data["timeout_ms"] = self.timeout_ms
return data
class NotImplementedError(ServerError):
"""未实现错误。
请求的操作尚未实现时抛出HTTP 501用于标记尚未落地的功能分支
避免静默返回空结果掩盖功能缺失
不透传 ``category_hint``未实现属功能缺失语义不涉及传输分类映射
翻译器按 ``error_code`` 映射 HTTP 501无需显式覆盖传输分类
"""
error_code = "NOT_IMPLEMENTED"
def __init__(
self,
operation: str,
*,
message: str | None = None,
trace_id: str | None = None,
details: dict[str, Any] | None = None,
) -> None:
"""初始化未实现错误。
Args:
operation: 未实现的操作名称写入 ``details`` 供客户端参考
message: 自定义错误信息 ``None`` 时使用默认模板
``"Operation not implemented: {operation}"``
trace_id: 调用链路追踪 ID用于跨链路关联
details: 额外业务字段 ``{"reason": "directory_disabled"}``
用于客户端区分同一 HTTP 状态码下的不同语义
"""
super().__init__(
message or f"Operation not implemented: {operation}",
trace_id=trace_id,
)
self.operation = operation
self.extra_details = details or {}
def to_dict(self) -> dict[str, Any]:
data = super().to_dict()
data["operation"] = self.operation
data.update(self.extra_details)
return data
class ServiceAccountCreationError(InternalError):
"""服务账号创建失败错误。
为渠道账户自动创建服务账号``User.user_type='service'``失败时抛出
HTTP 500``details`` 包含 ``channel_type````account_id``
``reason``便于定位创建失败的根因
适配器层抛出时应使用 ``raise ServiceAccountCreationError(...) from exc``
保留原始异常 traceback原始异常通过 ``cause`` 参数传递至
``InternalError.cause`` 字段
"""
error_code = "CHANNEL_SERVICE_ACCOUNT_CREATE_FAILED"
def __init__(
self,
channel_type: str,
account_id: str,
reason: str,
*,
cause: Error | Exception | None = None,
trace_id: str | None = None,
) -> None:
super().__init__(
cause=cause,
trace_id=trace_id,
message=f"failed to create service account for {channel_type}:{account_id}: {reason}",
)
self.channel_type = channel_type
self.account_id = account_id
self.reason = reason
def to_dict(self) -> dict[str, Any]:
data = super().to_dict()
data["channel_type"] = self.channel_type
data["account_id"] = self.account_id
data["reason"] = self.reason
return data