ForcePilot/backend/package/yuxi/channels/contract/errors/server.py
Kris 742299cb07 chore: 批量清理报告相关代码并完成多项功能迭代
本次提交包含多维度代码优化与功能增强:
1.  移除报告模块冗余导入与枚举,清理报表相关代码
2.  新增扫码登录支持方法与飞书适配器适配
3.  完善异常日志与健康检查信息
4.  扩展目录、配对管理、能力查询等接口
5.  优化出站管道与事务提交后钩子逻辑
6.  修复飞书消息解析与响应空值问题
7.  重构配置更新与服务账号创建逻辑
8.  统一传输错误分类契约与错误基类扩展
2026-07-06 20:49:35 +08:00

236 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""服务端错误类型定义。
定义 ``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