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

82 lines
3.3 KiB
Python
Raw Normal View History

"""错误基类定义。
定义所有契约层错误的根类型 ``Error``提供统一的 ``error_code`` / ``message`` /
``trace_id`` 字段与 ``to_dict()`` 序列化方法所有跨边界错误 **必须** 继承自
``Error``禁止原生异常穿透至核心层INV-7 / FF-ERR-01
"""
from __future__ import annotations
from abc import ABC
from typing import Any
class Error(Exception, ABC):
"""错误基类。
所有契约层错误的根类型子类通过类变量 ``error_code`` 声明稳定的错误码
便于驱动适配器映射为外部协议响应HTTP 状态码错误码
继承 ``Exception`` 以支持 ``raise`` 语句继承 ``ABC`` 以保留抽象基类
语义禁止直接实例化 ``Error``
字段
error_code: 稳定错误码由子类以类变量覆盖
message: 人类可读错误信息
trace_id: 调用链路追踪 ID由调用方显式注入用于跨链路关联INV-10
未注入时为空字符串契约层不再从 ContextVar 反向解析以避免
违反 §6.1契约层禁止依赖任何实现层
属性
status_code: HTTP 状态码 ``error_code`` ``CHANNEL_ERROR_STATUS_MAP``
得出未命中时默认 500
details: 业务字段字典 ``to_dict()`` 提取并排除 ``error_code`` /
``message`` / ``trace_id``子类覆写 ``to_dict()`` 后自动生效
"""
error_code: str = "ERROR"
def __init__(self, message: str, *, trace_id: str | None = None) -> None:
super().__init__(message)
self.message = message
# trace_id 由调用方显式注入契约层禁止依赖实现层§6.1
# 因此不再从 ContextVar 反向解析。adapter/model 层抛错时由其
# 自行注入当前 trace_idP1-5/P1-6。统一接受 ``str | None``
# ``None`` 归一化为空字符串,便于子类与 ``UnifiedError`` 协议
# ``trace_id: str | None``)一致。
self.trace_id = trace_id or ""
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}"