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