2026-07-02 03:22:12 +08:00
|
|
|
|
"""错误基类定义。
|
|
|
|
|
|
|
|
|
|
|
|
定义所有契约层错误的根类型 ``Error``,提供统一的 ``error_code`` / ``message`` /
|
2026-07-06 20:49:35 +08:00
|
|
|
|
``trace_id`` / ``category_hint`` 字段与 ``to_dict()`` 序列化方法。所有跨边界
|
|
|
|
|
|
错误 **必须** 继承自 ``Error``,禁止原生异常穿透至核心层(INV-7 / FF-ERR-01)。
|
2026-07-02 03:22:12 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from abc import ABC
|
2026-07-06 20:49:35 +08:00
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
|
# 仅用于类型注解:``from __future__ import annotations`` 使注解在运行时
|
|
|
|
|
|
# 为字符串,TYPE_CHECKING 块不会执行,避免与 ``errors.transport`` 的循环
|
|
|
|
|
|
# 导入(transport 模块运行时依赖 ``Error`` 基类)。
|
|
|
|
|
|
from yuxi.channels.contract.errors.transport import TransportErrorCategory
|
2026-07-02 03:22:12 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Error(Exception, ABC):
|
|
|
|
|
|
"""错误基类。
|
|
|
|
|
|
|
|
|
|
|
|
所有契约层错误的根类型。子类通过类变量 ``error_code`` 声明稳定的错误码,
|
|
|
|
|
|
便于驱动适配器映射为外部协议响应(HTTP 状态码、错误码)。
|
|
|
|
|
|
|
|
|
|
|
|
继承 ``Exception`` 以支持 ``raise`` 语句,继承 ``ABC`` 以保留抽象基类
|
|
|
|
|
|
语义(禁止直接实例化 ``Error``)。
|
|
|
|
|
|
|
|
|
|
|
|
字段:
|
|
|
|
|
|
error_code: 稳定错误码(由子类以类变量覆盖)。
|
|
|
|
|
|
message: 人类可读错误信息。
|
|
|
|
|
|
trace_id: 调用链路追踪 ID,由调用方显式注入,用于跨链路关联(INV-10)。
|
|
|
|
|
|
未注入时为空字符串,契约层不再从 ContextVar 反向解析,以避免
|
|
|
|
|
|
违反 §6.1(契约层禁止依赖任何实现层)。
|
2026-07-06 20:49:35 +08:00
|
|
|
|
category_hint: 传输分类建议(F-06)。插件构造 Error 时显式声明期望的
|
|
|
|
|
|
传输分类(``auth_expired`` / ``rate_limited`` / ``transient`` /
|
|
|
|
|
|
``permanent``),供翻译器读取以消除对 ``cause`` 字符串匹配的依赖。
|
|
|
|
|
|
默认 ``None``:旧插件不设置时翻译器走 isinstance 兜底逻辑,保持
|
|
|
|
|
|
向后兼容。
|
2026-07-02 03:22:12 +08:00
|
|
|
|
|
|
|
|
|
|
属性:
|
|
|
|
|
|
status_code: HTTP 状态码,由 ``error_code`` 查 ``CHANNEL_ERROR_STATUS_MAP``
|
|
|
|
|
|
得出,未命中时默认 500。
|
|
|
|
|
|
details: 业务字段字典,从 ``to_dict()`` 提取并排除 ``error_code`` /
|
|
|
|
|
|
``message`` / ``trace_id``,子类覆写 ``to_dict()`` 后自动生效。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
error_code: str = "ERROR"
|
|
|
|
|
|
|
2026-07-06 20:49:35 +08:00
|
|
|
|
def __init__(
|
|
|
|
|
|
self,
|
|
|
|
|
|
message: str,
|
|
|
|
|
|
*,
|
|
|
|
|
|
trace_id: str | None = None,
|
|
|
|
|
|
category_hint: TransportErrorCategory | None = None,
|
|
|
|
|
|
) -> None:
|
2026-07-02 03:22:12 +08:00
|
|
|
|
super().__init__(message)
|
|
|
|
|
|
self.message = message
|
|
|
|
|
|
# trace_id 由调用方显式注入:契约层禁止依赖实现层(§6.1),
|
|
|
|
|
|
# 因此不再从 ContextVar 反向解析。adapter/model 层抛错时由其
|
2026-07-03 19:18:13 +08:00
|
|
|
|
# 自行注入当前 trace_id(P1-5/P1-6)。统一接受 ``str | None``,
|
|
|
|
|
|
# ``None`` 归一化为空字符串,便于子类与 ``UnifiedError`` 协议
|
|
|
|
|
|
# (``trace_id: str | None``)一致。
|
|
|
|
|
|
self.trace_id = trace_id or ""
|
2026-07-06 20:49:35 +08:00
|
|
|
|
# category_hint 是"建议"而非"命令":翻译器可根据业务需要覆盖。
|
|
|
|
|
|
# 默认 None 保持向后兼容,旧插件不设置时翻译器走 isinstance 兜底。
|
|
|
|
|
|
self.category_hint = category_hint
|
2026-07-02 03:22:12 +08:00
|
|
|
|
|
|
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
|
|
|
|
"""序列化为字典,便于跨层传递、日志记录与 API 响应。"""
|
|
|
|
|
|
return {
|
|
|
|
|
|
"error_code": self.error_code,
|
|
|
|
|
|
"message": self.message,
|
|
|
|
|
|
"trace_id": self.trace_id,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def status_code(self) -> int:
|
|
|
|
|
|
"""HTTP 状态码:由 error_code 查表得出。
|
|
|
|
|
|
|
|
|
|
|
|
查询 ``CHANNEL_ERROR_STATUS_MAP``,未命中时返回 500。使用延迟 import
|
|
|
|
|
|
以避免核心层与状态码映射表之间的循环依赖。
|
|
|
|
|
|
"""
|
|
|
|
|
|
from yuxi.channels.contract.errors._status_map import (
|
|
|
|
|
|
CHANNEL_ERROR_STATUS_MAP,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return CHANNEL_ERROR_STATUS_MAP.get(self.error_code, 500)
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def details(self) -> dict[str, Any]:
|
|
|
|
|
|
"""业务字段:从 to_dict() 提取,排除 error_code/message/trace_id。
|
|
|
|
|
|
|
|
|
|
|
|
子类覆写的 to_dict() 自动生效,业务字段(resource/id/retry_after_ms 等)
|
|
|
|
|
|
自动进入 details。
|
|
|
|
|
|
"""
|
|
|
|
|
|
full_dict = self.to_dict()
|
|
|
|
|
|
return {k: v for k, v in full_dict.items() if k not in ("error_code", "message", "trace_id")}
|
|
|
|
|
|
|
|
|
|
|
|
def __str__(self) -> str:
|
|
|
|
|
|
return f"[{self.error_code}] {self.message}"
|