Skip to main content

sz_orm_core/
error.rs

1//! 错误类型与处理
2//!
3//! 全操作的集中错误类型定义
4
5use parking_lot::RwLock;
6use std::error::Error;
7use std::fmt;
8use std::io;
9use std::sync::{Arc, OnceLock};
10
11/// 错误上报 hook 类型
12type ErrorHook = Arc<dyn Fn(&DbError) + Send + Sync>;
13
14/// 全局错误上报 hook 存储(使用 OnceLock 实现 lazy 初始化,无需 once_cell 依赖)
15///
16/// P3-4 修复:使用 parking_lot::RwLock 替换 std::sync::RwLock,消除锁毒化风险
17/// (parking_lot 的 RwLock 不会返回 PoisonError,无需 unwrap())
18static GLOBAL_ERROR_HOOK: OnceLock<RwLock<Option<ErrorHook>>> = OnceLock::new();
19
20/// 获取全局错误 hook 存储的引用
21fn error_hook_storage() -> &'static RwLock<Option<ErrorHook>> {
22    GLOBAL_ERROR_HOOK.get_or_init(|| RwLock::new(None))
23}
24
25/// 设置全局错误上报 hook
26///
27/// 调用后,所有通过 `trigger_error_hook` 触发的错误都会被传入此 hook。
28pub fn set_error_hook(hook: ErrorHook) {
29    *error_hook_storage().write() = Some(hook);
30}
31
32/// 触发错误 hook(在 DbError 创建/返回时调用)
33///
34/// 如果未设置 hook,则静默跳过。
35pub fn trigger_error_hook(err: &DbError) {
36    let storage = error_hook_storage().read();
37    if let Some(ref hook) = *storage {
38        hook(err);
39    }
40}
41
42/// 数据库错误类型
43#[derive(Debug)]
44pub enum DbError {
45    /// 查询执行失败
46    QueryError(String),
47
48    /// 连接失败
49    ConnectionError(String),
50
51    /// 连接被拒绝
52    ConnectionRefused(String),
53
54    /// 连接超时
55    ConnectionTimeout(String),
56
57    /// 连接池错误
58    PoolError(PoolError),
59
60    /// 缓存错误
61    CacheError(CacheError),
62
63    /// 事务错误
64    TxError(TxError),
65
66    /// 迁移错误
67    MigrationError(String),
68
69    /// 方言不支持
70    Unsupported(String),
71
72    /// 配置错误
73    ConfigError(String),
74
75    /// 序列化/反序列化错误
76    SerdeError(String),
77
78    /// 未找到
79    NotFound(String),
80
81    /// 已存在
82    AlreadyExists(String),
83
84    /// 约束冲突(通用回退,无法确定具体类型时使用)
85    ConstraintViolation(String),
86
87    /// 唯一约束冲突(UNIQUE constraint)
88    UniqueViolation(String),
89
90    /// 外键约束冲突(FOREIGN KEY constraint)
91    ForeignKeyViolation(String),
92
93    /// 非空字段出现 null 值
94    NullValue(String),
95
96    /// 输入非法
97    InvalidInput(String),
98
99    /// 内部错误
100    Internal(String),
101
102    /// IO 错误
103    IoError(String),
104
105    /// 钩子执行失败
106    Hook(String),
107
108    /// 多租户错误(如租户 ID 缺失、跨租户访问)
109    TenantError(String),
110
111    /// 数据验证失败(业务规则校验未通过,由 before_validate 钩子触发)
112    Validation(String),
113
114    /// #6 修复:带上下文链的错误
115    ///
116    /// 包装原始错误 + 上下文链,用于在错误传播路径上附加调用方上下文。
117    /// 通过 `DbError::with_context("operation")` 创建。
118    Contextual {
119        /// 原始错误(Box 避免递归类型大小爆炸)
120        source: Box<DbError>,
121        /// 上下文链头节点
122        context: ErrorContext,
123    },
124}
125
126/// #6 修复:错误上下文链节点
127///
128/// 构成 `Context → Context → ...` 的单向链表,每一层记录
129/// `context`(操作描述)与 `span`(可选 tracing span 名称)。
130#[derive(Debug, Clone)]
131pub struct ErrorContext {
132    /// 当前层上下文描述(如 "fetching user by id")
133    pub context: String,
134    /// 可选 tracing span 名(如 "user_service")
135    pub span: Option<String>,
136    /// 上一层上下文(None 表示链尾)
137    pub previous: Option<Box<ErrorContext>>,
138}
139
140impl ErrorContext {
141    /// 创建新的上下文节点
142    pub fn new(context: impl Into<String>) -> Self {
143        Self {
144            context: context.into(),
145            span: None,
146            previous: None,
147        }
148    }
149
150    /// 附加上一层上下文(消费 self,返回新的链头)
151    pub fn with_previous(mut self, prev: ErrorContext) -> Self {
152        self.previous = Some(Box::new(prev));
153        self
154    }
155
156    /// 设置 tracing span 名
157    pub fn with_span(mut self, span: impl Into<String>) -> Self {
158        self.span = Some(span.into());
159        self
160    }
161
162    /// 遍历上下文链,从最外层到最内层
163    pub fn iter(&self) -> impl Iterator<Item = &ErrorContext> {
164        let mut current = Some(self);
165        std::iter::from_fn(move || {
166            let node = current?;
167            let result = node;
168            current = node.previous.as_deref();
169            Some(result)
170        })
171    }
172
173    /// 格式化为多行字符串(每行一个上下文层)
174    pub fn format_chain(&self) -> String {
175        self.iter()
176            .enumerate()
177            .map(|(i, ctx)| {
178                if let Some(ref span) = ctx.span {
179                    format!("  [{}] {} (span: {})", i, ctx.context, span)
180                } else {
181                    format!("  [{}] {}", i, ctx.context)
182                }
183            })
184            .collect::<Vec<_>>()
185            .join("\n")
186    }
187}
188
189impl DbError {
190    /// 新建查询错误
191    pub fn query(s: impl Into<String>) -> Self {
192        DbError::QueryError(s.into())
193    }
194
195    /// 新建连接错误
196    pub fn connection(s: impl Into<String>) -> Self {
197        DbError::ConnectionError(s.into())
198    }
199
200    /// 新建未找到错误
201    pub fn not_found(s: impl Into<String>) -> Self {
202        DbError::NotFound(s.into())
203    }
204
205    /// #6 修复:附加错误上下文(消费 self,返回带上下文的新错误)
206    ///
207    /// 用于在错误传播路径上附加调用方上下文,形成 `error.with_context("operation")`
208    /// 链式调用。多次调用会形成上下文链表。
209    ///
210    /// # 示例
211    ///
212    /// ```ignore
213    /// fn fetch_user(id: i64) -> Result<User, DbError> {
214    ///     db.query("SELECT * FROM users WHERE id = ?", &[id.into()])
215    ///         .await
216    ///         .map_err(|e| e.with_context(format!("fetching user id={}", id)))?;
217    ///     // ...
218    /// }
219    /// ```
220    pub fn with_context(self, context: impl Into<String>) -> Self {
221        let new_ctx = ErrorContext::new(context);
222        // 若已是 Contextual,将原 context 链作为 previous
223        match self {
224            DbError::Contextual {
225                source,
226                context: existing_ctx,
227            } => {
228                let new_ctx = new_ctx.with_previous(existing_ctx);
229                DbError::Contextual {
230                    source,
231                    context: new_ctx,
232                }
233            }
234            other => DbError::Contextual {
235                source: Box::new(other),
236                context: new_ctx,
237            },
238        }
239    }
240
241    /// #6 修复:附加错误上下文(含 tracing span 名)
242    pub fn with_context_in_span(self, context: impl Into<String>, span: impl Into<String>) -> Self {
243        let new_ctx = ErrorContext::new(context).with_span(span);
244        match self {
245            DbError::Contextual {
246                source,
247                context: existing_ctx,
248            } => {
249                let new_ctx = new_ctx.with_previous(existing_ctx);
250                DbError::Contextual {
251                    source,
252                    context: new_ctx,
253                }
254            }
255            other => DbError::Contextual {
256                source: Box::new(other),
257                context: new_ctx,
258            },
259        }
260    }
261
262    /// #6 修复:获取错误上下文链(None 表示无附加上下文)
263    pub fn context(&self) -> Option<&ErrorContext> {
264        match self {
265            DbError::Contextual { context, .. } => Some(context),
266            _ => None,
267        }
268    }
269
270    /// #6 修复:格式化错误上下文链为多行字符串
271    pub fn format_context_chain(&self) -> String {
272        match self {
273            DbError::Contextual { context, .. } => context.format_chain(),
274            _ => String::new(),
275        }
276    }
277
278    /// #6 修复:剥离上下文链,返回原始错误引用
279    pub fn root_cause(&self) -> &DbError {
280        match self {
281            DbError::Contextual { source, .. } => source.root_cause(),
282            other => other,
283        }
284    }
285
286    /// 该错误是否可重试
287    pub fn is_retryable(&self) -> bool {
288        self.root_cause_is_retryable()
289    }
290
291    /// 内部方法:检查根错误是否可重试
292    fn root_cause_is_retryable(&self) -> bool {
293        match self {
294            DbError::Contextual { source, .. } => source.root_cause_is_retryable(),
295            DbError::ConnectionError(_)
296            | DbError::ConnectionTimeout(_)
297            | DbError::PoolError(PoolError::Timeout) => true,
298            _ => false,
299        }
300    }
301
302    /// 获取错误码(用于日志/监控)
303    pub fn error_code(&self) -> &'static str {
304        match self {
305            DbError::Contextual { source, .. } => source.error_code(),
306            DbError::QueryError(_) => "DB001",
307            DbError::ConnectionError(_) => "DB002",
308            DbError::ConnectionRefused(_) => "DB003",
309            DbError::ConnectionTimeout(_) => "DB004",
310            DbError::PoolError(e) => e.error_code(),
311            DbError::CacheError(e) => e.error_code(),
312            DbError::TxError(_) => "DB007",
313            DbError::MigrationError(_) => "DB008",
314            DbError::Unsupported(_) => "DB009",
315            DbError::ConfigError(_) => "DB010",
316            DbError::SerdeError(_) => "DB011",
317            DbError::NotFound(_) => "DB012",
318            DbError::AlreadyExists(_) => "DB013",
319            DbError::ConstraintViolation(_) => "DB014",
320            DbError::UniqueViolation(_) => "DB022",
321            DbError::ForeignKeyViolation(_) => "DB023",
322            DbError::NullValue(_) => "DB015",
323            DbError::InvalidInput(_) => "DB016",
324            DbError::Internal(_) => "DB017",
325            DbError::IoError(_) => "DB018",
326            DbError::Hook(_) => "DB019",
327            DbError::TenantError(_) => "DB020",
328            DbError::Validation(_) => "DB021",
329        }
330    }
331
332    /// 映射到 HTTP 状态码(RFC 7231)
333    ///
334    /// 用于在 HTTP 服务(如 axum/actix)中根据数据库错误返回合适的 HTTP 状态码。
335    /// - 400 Bad Request:非法输入、参数校验失败、配置错误
336    /// - 404 Not Found:资源未找到
337    /// - 409 Conflict:资源已存在、约束冲突(唯一/外键/非空/通用)
338    /// - 422 Unprocessable Entity:序列化/反序列化错误
339    /// - 500 Internal Server Error:查询失败、内部错误、钩子失败、迁移失败、IO 错误、事务错误、租户错误
340    /// - 501 Not Implemented:方言/功能不支持
341    /// - 502 Bad Gateway:连接错误、连接被拒绝
342    /// - 503 Service Unavailable:连接池耗尽/关闭、缓存错误
343    /// - 504 Gateway Timeout:连接超时、连接池获取超时
344    pub fn http_status(&self) -> u16 {
345        match self {
346            DbError::Contextual { source, .. } => source.http_status(),
347            DbError::InvalidInput(_) | DbError::Validation(_) | DbError::ConfigError(_) => 400,
348            DbError::NotFound(_) => 404,
349            DbError::AlreadyExists(_)
350            | DbError::ConstraintViolation(_)
351            | DbError::UniqueViolation(_)
352            | DbError::ForeignKeyViolation(_)
353            | DbError::NullValue(_) => 409,
354            DbError::SerdeError(_) => 422,
355            DbError::Unsupported(_) => 501,
356            DbError::ConnectionError(_) | DbError::ConnectionRefused(_) => 502,
357            DbError::ConnectionTimeout(_) => 504,
358            DbError::PoolError(e) => match e {
359                PoolError::Timeout => 504,
360                PoolError::Exhausted | PoolError::Closed | PoolError::ConnectionFailed(_) => 503,
361                _ => 500,
362            },
363            DbError::CacheError(_) => 503,
364            // QueryError/Internal/Hook/MigrationError/IoError/TxError/TenantError 均为服务端内部错误
365            _ => 500,
366        }
367    }
368
369    /// 映射到 gRPC 状态码
370    ///
371    /// 用于在 gRPC 服务(tonic)中根据数据库错误返回合适的 gRPC 状态码。
372    /// 参考:https://grpc.io/docs/guides/status-codes/
373    /// - 2 UNKNOWN:查询失败、内部错误、钩子失败、迁移失败、IO 错误
374    /// - 3 INVALID_ARGUMENT:非法输入、参数校验失败、配置错误
375    /// - 4 DEADLINE_EXCEEDED:连接超时、连接池获取超时
376    /// - 5 NOT_FOUND:资源未找到
377    /// - 6 ALREADY_EXISTS:资源已存在、唯一约束冲突
378    /// - 7 PERMISSION_DENIED:租户错误(跨租户访问)
379    /// - 8 RESOURCE_EXHAUSTED:连接池耗尽/关闭、缓存错误
380    /// - 9 FAILED_PRECONDITION:约束冲突(通用/外键/非空)、事务错误
381    /// - 12 UNIMPLEMENTED:方言/功能不支持
382    /// - 13 INTERNAL:序列化/反序列化错误
383    /// - 14 UNAVAILABLE:连接错误、连接被拒绝、连接创建失败
384    pub fn grpc_status_code(&self) -> u32 {
385        match self {
386            DbError::Contextual { source, .. } => source.grpc_status_code(),
387            DbError::InvalidInput(_) | DbError::Validation(_) | DbError::ConfigError(_) => 3,
388            DbError::ConnectionTimeout(_) => 4,
389            DbError::PoolError(PoolError::Timeout) => 4,
390            DbError::NotFound(_) => 5,
391            DbError::AlreadyExists(_) | DbError::UniqueViolation(_) => 6,
392            DbError::TenantError(_) => 7,
393            DbError::PoolError(PoolError::Exhausted) | DbError::PoolError(PoolError::Closed) => 8,
394            DbError::CacheError(_) => 8,
395            DbError::ConstraintViolation(_)
396            | DbError::ForeignKeyViolation(_)
397            | DbError::NullValue(_)
398            | DbError::TxError(_) => 9,
399            DbError::Unsupported(_) => 12,
400            DbError::SerdeError(_) => 13,
401            DbError::ConnectionError(_)
402            | DbError::ConnectionRefused(_)
403            | DbError::PoolError(PoolError::ConnectionFailed(_)) => 14,
404            // QueryError/Internal/Hook/MigrationError/IoError/PoolError(其他) → UNKNOWN
405            _ => 2,
406        }
407    }
408}
409
410impl fmt::Display for DbError {
411    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412        match self {
413            DbError::QueryError(s) => write!(f, "Query error: {}", s),
414            DbError::ConnectionError(s) => write!(f, "Connection error: {}", s),
415            DbError::ConnectionRefused(s) => write!(f, "Connection refused: {}", s),
416            DbError::ConnectionTimeout(s) => write!(f, "Connection timeout: {}", s),
417            DbError::PoolError(e) => write!(f, "Pool error: {}", e),
418            DbError::CacheError(e) => write!(f, "Cache error: {}", e),
419            DbError::TxError(e) => write!(f, "Transaction error: {}", e),
420            DbError::MigrationError(s) => write!(f, "Migration error: {}", s),
421            DbError::Unsupported(s) => write!(f, "Unsupported: {}", s),
422            DbError::ConfigError(s) => write!(f, "Configuration error: {}", s),
423            DbError::SerdeError(s) => write!(f, "Serialization error: {}", s),
424            DbError::NotFound(s) => write!(f, "Not found: {}", s),
425            DbError::AlreadyExists(s) => write!(f, "Already exists: {}", s),
426            DbError::ConstraintViolation(s) => write!(f, "Constraint violation: {}", s),
427            DbError::UniqueViolation(s) => write!(f, "Unique constraint violation: {}", s),
428            DbError::ForeignKeyViolation(s) => write!(f, "Foreign key constraint violation: {}", s),
429            DbError::NullValue(s) => write!(f, "Null value: {}", s),
430            DbError::InvalidInput(s) => write!(f, "Invalid input: {}", s),
431            DbError::Internal(s) => write!(f, "Internal error: {}", s),
432            DbError::IoError(s) => write!(f, "IO error: {}", s),
433            DbError::Hook(s) => write!(f, "Hook error: {}", s),
434            DbError::TenantError(s) => write!(f, "Tenant error: {}", s),
435            DbError::Validation(s) => write!(f, "Validation error: {}", s),
436            DbError::Contextual {
437                context, source, ..
438            } => write!(f, "{}: {}", context.context, source),
439        }
440    }
441}
442
443impl Error for DbError {
444    fn source(&self) -> Option<&(dyn Error + 'static)> {
445        match self {
446            DbError::PoolError(e) => Some(e),
447            DbError::CacheError(e) => Some(e),
448            DbError::TxError(e) => Some(e),
449            // #6 修复:暴露 Contextual 包装的原始错误,使 std::error::Error::source()
450            // 链式遍历可透过 Contextual 层到达根因
451            DbError::Contextual { source, .. } => Some(source.as_ref()),
452            _ => None,
453        }
454    }
455}
456
457impl From<io::Error> for DbError {
458    fn from(err: io::Error) -> Self {
459        DbError::IoError(err.to_string())
460    }
461}
462
463impl From<serde_json::Error> for DbError {
464    fn from(err: serde_json::Error) -> Self {
465        DbError::SerdeError(err.to_string())
466    }
467}
468
469impl From<std::num::TryFromIntError> for DbError {
470    fn from(err: std::num::TryFromIntError) -> Self {
471        DbError::Internal(err.to_string())
472    }
473}
474
475impl From<std::string::FromUtf8Error> for DbError {
476    fn from(err: std::string::FromUtf8Error) -> Self {
477        DbError::Internal(err.to_string())
478    }
479}
480
481impl<T> From<std::sync::PoisonError<T>> for DbError {
482    fn from(err: std::sync::PoisonError<T>) -> Self {
483        DbError::Internal(format!("RwLock/Mutex poisoned: {}", err))
484    }
485}
486
487/// 连接池特有错误
488#[derive(Debug)]
489pub enum PoolError {
490    /// 连接池耗尽
491    Exhausted,
492
493    /// 获取连接超时
494    Timeout,
495
496    /// 连接已被获取
497    AlreadyAcquired,
498
499    /// 连接未被获取
500    NotAcquired,
501
502    /// 配置非法
503    InvalidConfig(String),
504
505    /// 内部错误
506    Internal(String),
507
508    /// 连接池已关闭(close_all 后拒绝新 acquire)
509    Closed,
510
511    /// 连接创建失败(保留原始错误信息)
512    ConnectionFailed(String),
513
514    /// #88 修复:断路器已跳闸,拒绝请求以防级联失败
515    ///
516    /// 当 `circuit-breaker` feature 启用且 `CircuitBreaker` 处于 `Open` 状态时,
517    /// `acquire`/`query_with_timeout` 等方法会返回此错误,避免对下游数据库
518    /// 造成更大压力。
519    CircuitOpen,
520
521    /// #93 修复:限流器拒绝请求
522    ///
523    /// 当 `rate-limit` feature 启用且 `RateLimiter` 拒绝当前 key 时返回。
524    /// `remaining` 为本次窗口剩余配额(已为 0),`reset_at` 为窗口重置时间戳(毫秒)。
525    RateLimited { remaining: u64, reset_at: i64 },
526}
527
528impl PoolError {
529    pub fn error_code(&self) -> &'static str {
530        match self {
531            PoolError::Exhausted => "PL001",
532            PoolError::Timeout => "PL002",
533            PoolError::AlreadyAcquired => "PL003",
534            PoolError::NotAcquired => "PL004",
535            PoolError::InvalidConfig(_) => "PL005",
536            PoolError::Internal(_) => "PL006",
537            PoolError::Closed => "PL007",
538            PoolError::ConnectionFailed(_) => "PL008",
539            PoolError::CircuitOpen => "PL009",
540            PoolError::RateLimited { .. } => "PL010",
541        }
542    }
543}
544
545impl fmt::Display for PoolError {
546    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
547        match self {
548            PoolError::Exhausted => write!(f, "Connection pool exhausted"),
549            PoolError::Timeout => write!(f, "Connection acquire timeout"),
550            PoolError::AlreadyAcquired => write!(f, "Connection already acquired"),
551            PoolError::NotAcquired => write!(f, "Connection not acquired"),
552            PoolError::InvalidConfig(s) => write!(f, "Invalid pool config: {}", s),
553            PoolError::Internal(s) => write!(f, "Internal pool error: {}", s),
554            PoolError::Closed => write!(f, "Connection pool closed"),
555            PoolError::ConnectionFailed(s) => write!(f, "Connection failed: {}", s),
556            PoolError::CircuitOpen => write!(f, "Circuit breaker open"),
557            PoolError::RateLimited {
558                remaining,
559                reset_at,
560            } => write!(
561                f,
562                "Rate limited (remaining: {}, reset_at: {})",
563                remaining, reset_at
564            ),
565        }
566    }
567}
568
569impl Error for PoolError {}
570
571/// 缓存特有错误
572#[derive(Debug, Clone)]
573pub enum CacheError {
574    /// 键不存在
575    NotFound(String),
576
577    /// 序列化错误
578    SerializationError(String),
579
580    /// 反序列化错误
581    DeserializationError(String),
582
583    /// 连接错误
584    ConnectionError(String),
585
586    /// 超时
587    Timeout(String),
588
589    /// 内部错误
590    Internal(String),
591}
592
593impl CacheError {
594    pub fn error_code(&self) -> &'static str {
595        match self {
596            CacheError::NotFound(_) => "CH001",
597            CacheError::SerializationError(_) => "CH002",
598            CacheError::DeserializationError(_) => "CH003",
599            CacheError::ConnectionError(_) => "CH004",
600            CacheError::Timeout(_) => "CH005",
601            CacheError::Internal(_) => "CH006",
602        }
603    }
604}
605
606impl fmt::Display for CacheError {
607    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
608        match self {
609            CacheError::NotFound(s) => write!(f, "Cache key not found: {}", s),
610            CacheError::SerializationError(s) => write!(f, "Cache serialization error: {}", s),
611            CacheError::DeserializationError(s) => write!(f, "Cache deserialization error: {}", s),
612            CacheError::ConnectionError(s) => write!(f, "Cache connection error: {}", s),
613            CacheError::Timeout(s) => write!(f, "Cache timeout: {}", s),
614            CacheError::Internal(s) => write!(f, "Cache internal error: {}", s),
615        }
616    }
617}
618
619impl Error for CacheError {}
620
621impl<T> From<std::sync::PoisonError<T>> for CacheError {
622    fn from(err: std::sync::PoisonError<T>) -> Self {
623        CacheError::Internal(format!("RwLock poisoned: {}", err))
624    }
625}
626
627/// 事务状态
628///
629/// 定义在 `error` 模块以避免 `transaction` ↔ `error` 循环依赖,
630/// `transaction` 模块通过 `pub use` 重导出本类型。
631#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
632pub enum TransactionState {
633    #[default]
634    Active,
635    Committed,
636    RolledBack,
637}
638
639impl fmt::Display for TransactionState {
640    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
641        match self {
642            TransactionState::Active => write!(f, "Active"),
643            TransactionState::Committed => write!(f, "Committed"),
644            TransactionState::RolledBack => write!(f, "RolledBack"),
645        }
646    }
647}
648
649/// 事务特有错误
650#[derive(Debug)]
651pub enum TxError {
652    /// 事务未开始
653    NotStarted,
654
655    /// 事务已开始
656    AlreadyStarted,
657
658    /// 事务提交失败
659    CommitFailed(String),
660
661    /// 事务回滚失败
662    RollbackFailed(String),
663
664    /// 保存点错误
665    SavepointError(String),
666
667    /// 不支持嵌套事务
668    NestedNotSupported,
669
670    /// 事务不在 Active 状态(用于 execute/query 等操作前置校验)
671    NotActive(TransactionState),
672
673    /// 保存点名称非法(包含不支持的字符或以数字开头)
674    InvalidSavepointName(String),
675
676    /// 连接已被取走(take_connection 重复调用,或操作时连接已释放)
677    ConnectionTaken,
678
679    /// H-8 修复:嵌套事务深度超过限制
680    ///
681    /// `current_depth` 为当前已嵌套深度(含本次),`max_depth` 为配置的最大深度。
682    MaxNestingDepthExceeded { current_depth: u32, max_depth: u32 },
683
684    /// M-8 修复:死锁检测
685    ///
686    /// 当事务执行过程中检测到死锁(数据库返回死锁错误码)时返回。
687    /// 调用方可使用 `retry_on_deadlock` 包装器自动重试。
688    DeadlockDetected { attempt: u32, max_attempts: u32 },
689}
690
691impl fmt::Display for TxError {
692    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
693        match self {
694            TxError::NotStarted => write!(f, "Transaction not started"),
695            TxError::AlreadyStarted => write!(f, "Transaction already started"),
696            TxError::CommitFailed(s) => write!(f, "Transaction commit failed: {}", s),
697            TxError::RollbackFailed(s) => write!(f, "Transaction rollback failed: {}", s),
698            TxError::SavepointError(s) => write!(f, "Savepoint error: {}", s),
699            TxError::NestedNotSupported => write!(f, "Nested transactions not supported"),
700            TxError::NotActive(state) => {
701                write!(f, "Transaction not active (current state: {})", state)
702            }
703            TxError::InvalidSavepointName(name) => {
704                write!(
705                    f,
706                    "Invalid savepoint name '{}': must be non-empty, start with a letter or underscore, and contain only ASCII alphanumeric or underscore",
707                    name
708                )
709            }
710            TxError::ConnectionTaken => write!(f, "Transaction connection already taken"),
711            TxError::MaxNestingDepthExceeded {
712                current_depth,
713                max_depth,
714            } => write!(
715                f,
716                "Transaction nesting depth {} exceeds maximum allowed {}",
717                current_depth, max_depth
718            ),
719            TxError::DeadlockDetected {
720                attempt,
721                max_attempts,
722            } => write!(
723                f,
724                "Deadlock detected on attempt {} of {}",
725                attempt, max_attempts
726            ),
727        }
728    }
729}
730
731impl Error for TxError {
732    fn source(&self) -> Option<&(dyn Error + 'static)> {
733        // TxError 各变体仅承载 String 描述或状态枚举(无嵌套 Error 对象),故无 source 可委托
734        None
735    }
736}
737
738#[cfg(test)]
739mod tests {
740    use super::*;
741
742    #[test]
743    fn test_db_error_display() {
744        let err = DbError::query("test");
745        assert_eq!(format!("{}", err), "Query error: test");
746
747        let err = DbError::not_found("user");
748        assert_eq!(format!("{}", err), "Not found: user");
749    }
750
751    #[test]
752    fn test_db_error_code() {
753        let err = DbError::query("test");
754        assert_eq!(err.error_code(), "DB001");
755
756        let err = DbError::PoolError(PoolError::Timeout);
757        assert_eq!(err.error_code(), "PL002");
758    }
759
760    #[test]
761    fn test_db_error_source() {
762        let err = DbError::PoolError(PoolError::Timeout);
763        assert!(err.source().is_some());
764    }
765
766    #[test]
767    fn test_db_error_contextual_source_chain() {
768        // #6 修复验证:Contextual 错误的 source() 应直接指向根错误,
769        // 上下文链通过 ErrorContext.previous 维护(不通过 source() 链)
770        let root = DbError::QueryError("table not found".to_string());
771        let wrapped = root.with_context("fetching user");
772        let outer = wrapped.with_context("user_service.fetch");
773
774        // 1. std::error::Error::source() 应直接返回根 QueryError(跳过 Contextual 层)
775        let source1 = outer.source().expect("outer should have source");
776        // source1 应为根 QueryError,不再有 source
777        assert!(source1.source().is_none());
778
779        // 2. 上下文链应有两层:外层 "user_service.fetch",内层 "fetching user"
780        let ctx_chain = outer.context().expect("outer should have context");
781        assert_eq!(ctx_chain.context, "user_service.fetch");
782        let inner_ctx = ctx_chain
783            .previous
784            .as_ref()
785            .expect("should have previous context");
786        assert_eq!(inner_ctx.context, "fetching user");
787        assert!(inner_ctx.previous.is_none());
788
789        // 3. root_cause() 应返回根 QueryError
790        let root_cause = outer.root_cause();
791        assert!(matches!(root_cause, DbError::QueryError(_)));
792    }
793
794    #[test]
795    fn test_pool_error() {
796        let err = PoolError::Timeout;
797        assert_eq!(format!("{}", err), "Connection acquire timeout");
798        assert_eq!(err.error_code(), "PL002");
799    }
800
801    #[test]
802    fn test_cache_error() {
803        let err = CacheError::NotFound("key".to_string());
804        assert_eq!(format!("{}", err), "Cache key not found: key");
805        assert_eq!(err.error_code(), "CH001");
806    }
807
808    #[test]
809    fn test_error_hook_set_and_trigger() {
810        use std::sync::atomic::{AtomicU32, Ordering};
811        let counter = Arc::new(AtomicU32::new(0));
812        let c = counter.clone();
813        set_error_hook(Arc::new(move |_err: &DbError| {
814            c.fetch_add(1, Ordering::SeqCst);
815        }));
816        let err = DbError::query("hook test");
817        trigger_error_hook(&err);
818        assert_eq!(counter.load(Ordering::SeqCst), 1);
819    }
820
821    #[test]
822    fn test_error_hook_no_hook_silent() {
823        // 不设置 hook 时 trigger 应静默跳过(不 panic)
824        let err = DbError::query("no hook");
825        trigger_error_hook(&err);
826    }
827
828    // ===== HTTP 状态码映射测试 =====
829
830    #[test]
831    fn test_http_status_bad_request() {
832        assert_eq!(DbError::InvalidInput("bad".into()).http_status(), 400);
833        assert_eq!(DbError::Validation("fail".into()).http_status(), 400);
834        assert_eq!(DbError::ConfigError("cfg".into()).http_status(), 400);
835    }
836
837    #[test]
838    fn test_http_status_not_found() {
839        assert_eq!(DbError::NotFound("user".into()).http_status(), 404);
840    }
841
842    #[test]
843    fn test_http_status_conflict() {
844        assert_eq!(DbError::AlreadyExists("x".into()).http_status(), 409);
845        assert_eq!(DbError::ConstraintViolation("c".into()).http_status(), 409);
846        assert_eq!(DbError::UniqueViolation("u".into()).http_status(), 409);
847        assert_eq!(DbError::ForeignKeyViolation("f".into()).http_status(), 409);
848        assert_eq!(DbError::NullValue("n".into()).http_status(), 409);
849    }
850
851    #[test]
852    fn test_http_status_unprocessable() {
853        assert_eq!(DbError::SerdeError("s".into()).http_status(), 422);
854    }
855
856    #[test]
857    fn test_http_status_internal_server_error() {
858        assert_eq!(DbError::QueryError("q".into()).http_status(), 500);
859        assert_eq!(DbError::Internal("i".into()).http_status(), 500);
860        assert_eq!(DbError::Hook("h".into()).http_status(), 500);
861        assert_eq!(DbError::MigrationError("m".into()).http_status(), 500);
862        assert_eq!(DbError::IoError("io".into()).http_status(), 500);
863        assert_eq!(DbError::TxError(TxError::NotStarted).http_status(), 500);
864        assert_eq!(DbError::TenantError("t".into()).http_status(), 500);
865    }
866
867    #[test]
868    fn test_http_status_not_implemented() {
869        assert_eq!(DbError::Unsupported("feat".into()).http_status(), 501);
870    }
871
872    #[test]
873    fn test_http_status_bad_gateway() {
874        assert_eq!(DbError::ConnectionError("c".into()).http_status(), 502);
875        assert_eq!(DbError::ConnectionRefused("r".into()).http_status(), 502);
876    }
877
878    #[test]
879    fn test_http_status_service_unavailable() {
880        assert_eq!(DbError::PoolError(PoolError::Exhausted).http_status(), 503);
881        assert_eq!(DbError::PoolError(PoolError::Closed).http_status(), 503);
882        assert_eq!(
883            DbError::PoolError(PoolError::ConnectionFailed("f".into())).http_status(),
884            503
885        );
886        assert_eq!(
887            DbError::CacheError(CacheError::Internal("e".into())).http_status(),
888            503
889        );
890    }
891
892    #[test]
893    fn test_http_status_gateway_timeout() {
894        assert_eq!(DbError::ConnectionTimeout("t".into()).http_status(), 504);
895        assert_eq!(DbError::PoolError(PoolError::Timeout).http_status(), 504);
896    }
897
898    // ===== gRPC 状态码映射测试 =====
899
900    #[test]
901    fn test_grpc_status_invalid_argument() {
902        assert_eq!(DbError::InvalidInput("bad".into()).grpc_status_code(), 3);
903        assert_eq!(DbError::Validation("fail".into()).grpc_status_code(), 3);
904        assert_eq!(DbError::ConfigError("cfg".into()).grpc_status_code(), 3);
905    }
906
907    #[test]
908    fn test_grpc_status_deadline_exceeded() {
909        assert_eq!(DbError::ConnectionTimeout("t".into()).grpc_status_code(), 4);
910        assert_eq!(DbError::PoolError(PoolError::Timeout).grpc_status_code(), 4);
911    }
912
913    #[test]
914    fn test_grpc_status_not_found() {
915        assert_eq!(DbError::NotFound("user".into()).grpc_status_code(), 5);
916    }
917
918    #[test]
919    fn test_grpc_status_already_exists() {
920        assert_eq!(DbError::AlreadyExists("x".into()).grpc_status_code(), 6);
921        assert_eq!(DbError::UniqueViolation("u".into()).grpc_status_code(), 6);
922    }
923
924    #[test]
925    fn test_grpc_status_permission_denied() {
926        assert_eq!(DbError::TenantError("t".into()).grpc_status_code(), 7);
927    }
928
929    #[test]
930    fn test_grpc_status_resource_exhausted() {
931        assert_eq!(
932            DbError::PoolError(PoolError::Exhausted).grpc_status_code(),
933            8
934        );
935        assert_eq!(DbError::PoolError(PoolError::Closed).grpc_status_code(), 8);
936        assert_eq!(
937            DbError::CacheError(CacheError::Internal("e".into())).grpc_status_code(),
938            8
939        );
940    }
941
942    #[test]
943    fn test_grpc_status_failed_precondition() {
944        assert_eq!(
945            DbError::ConstraintViolation("c".into()).grpc_status_code(),
946            9
947        );
948        assert_eq!(
949            DbError::ForeignKeyViolation("f".into()).grpc_status_code(),
950            9
951        );
952        assert_eq!(DbError::NullValue("n".into()).grpc_status_code(), 9);
953        assert_eq!(DbError::TxError(TxError::NotStarted).grpc_status_code(), 9);
954    }
955
956    #[test]
957    fn test_grpc_status_unimplemented() {
958        assert_eq!(DbError::Unsupported("feat".into()).grpc_status_code(), 12);
959    }
960
961    #[test]
962    fn test_grpc_status_internal() {
963        assert_eq!(DbError::SerdeError("s".into()).grpc_status_code(), 13);
964    }
965
966    #[test]
967    fn test_grpc_status_unavailable() {
968        assert_eq!(DbError::ConnectionError("c".into()).grpc_status_code(), 14);
969        assert_eq!(
970            DbError::ConnectionRefused("r".into()).grpc_status_code(),
971            14
972        );
973        assert_eq!(
974            DbError::PoolError(PoolError::ConnectionFailed("f".into())).grpc_status_code(),
975            14
976        );
977    }
978
979    #[test]
980    fn test_grpc_status_unknown() {
981        assert_eq!(DbError::QueryError("q".into()).grpc_status_code(), 2);
982        assert_eq!(DbError::Internal("i".into()).grpc_status_code(), 2);
983        assert_eq!(DbError::Hook("h".into()).grpc_status_code(), 2);
984        assert_eq!(DbError::MigrationError("m".into()).grpc_status_code(), 2);
985        assert_eq!(DbError::IoError("io".into()).grpc_status_code(), 2);
986        // PoolError 的其他变体(AlreadyAcquired/NotAcquired/InvalidConfig/Internal)→ UNKNOWN
987        assert_eq!(
988            DbError::PoolError(PoolError::AlreadyAcquired).grpc_status_code(),
989            2
990        );
991        assert_eq!(
992            DbError::PoolError(PoolError::NotAcquired).grpc_status_code(),
993            2
994        );
995        assert_eq!(
996            DbError::PoolError(PoolError::InvalidConfig("x".into())).grpc_status_code(),
997            2
998        );
999        assert_eq!(
1000            DbError::PoolError(PoolError::Internal("y".into())).grpc_status_code(),
1001            2
1002        );
1003    }
1004}