Skip to main content

openlark_core/error/
core.rs

1//! CoreError:基于 thiserror 的企业级错误处理
2//!
3//! 目标:展示单一映射来源(ErrorCode),简化字段、统一严重度/可重试策略,
4//! 并保留可序列化的 ErrorRecord 供观测与分析使用。
5
6use std::{borrow::Cow, time::Duration};
7
8use super::{
9    codes::ErrorCode,
10    context::ErrorContext,
11    traits::{ErrorSeverity, ErrorTrait, ErrorType},
12};
13use serde::Serialize;
14use thiserror::Error;
15
16// 重试与恢复策略
17
18/// 重试策略(供 CoreError 使用)
19#[derive(Debug, Clone)]
20pub struct RetryPolicy {
21    /// 最大重试次数
22    pub max_retries: u32,
23    /// 寏次重试基础延迟
24    pub base_delay: Duration,
25    /// 退避因子
26    pub backoff_factor: f64,
27    /// 最大延迟
28    pub max_delay: Option<Duration>,
29}
30
31impl RetryPolicy {
32    /// 创建不重试策略
33    pub fn no_retry() -> Self {
34        Self {
35            max_retries: 0,
36            base_delay: Duration::from_secs(1),
37            backoff_factor: 1.0,
38            max_delay: None,
39        }
40    }
41
42    /// 创建固定延迟重试策略
43    pub fn fixed(max_retries: u32, delay: Duration) -> Self {
44        Self {
45            max_retries,
46            base_delay: delay,
47            backoff_factor: 1.0,
48            max_delay: Some(delay),
49        }
50    }
51
52    /// 创建指数退避重试策略
53    pub fn exponential(max_retries: u32, base_delay: Duration) -> Self {
54        Self {
55            max_retries,
56            base_delay,
57            backoff_factor: 2.0,
58            max_delay: Some(Duration::from_secs(300)), // 5分钟最大延迟
59        }
60    }
61
62    /// 是否可重试
63    pub fn is_retryable(&self) -> bool {
64        self.max_retries > 0
65    }
66
67    /// 计算重试延迟
68    pub fn retry_delay(&self, attempt: u32) -> Option<Duration> {
69        if attempt >= self.max_retries {
70            return None;
71        }
72
73        let delay = if self.backoff_factor == 1.0 {
74            self.base_delay
75        } else {
76            let seconds = self.base_delay.as_secs_f64() * self.backoff_factor.powi(attempt as i32);
77            Duration::from_secs_f64(seconds)
78        };
79
80        Some(self.max_delay.map_or(delay, |max| delay.min(max)))
81    }
82
83    /// 直接获取延迟(超过最大重试返回0)
84    pub fn delay(&self, attempt: u32) -> Duration {
85        self.retry_delay(attempt).unwrap_or(Duration::ZERO)
86    }
87
88    /// 是否使用指数退避
89    pub fn use_exponential_backoff(&self) -> bool {
90        self.backoff_factor > 1.0
91    }
92
93    /// 最大重试次数(保持旧接口)
94    pub fn max_retries(&self) -> u32 {
95        self.max_retries
96    }
97}
98
99impl Default for RetryPolicy {
100    fn default() -> Self {
101        Self::exponential(3, Duration::from_secs(1))
102    }
103}
104
105/// 恢复策略
106#[derive(Debug, Clone)]
107pub enum RecoveryStrategy {
108    /// 重试并使用指数退避
109    RetryWithBackoff,
110    /// 验证后重试
111    ValidateAndRetry,
112    /// 重新认证
113    Reauthenticate,
114    /// 请求权限
115    RequestPermission,
116    /// 手动干预
117    ManualIntervention,
118    /// 延迟重试
119    RetryWithDelay,
120}
121
122type AnyError = Box<dyn std::error::Error + Send + Sync>;
123
124/// 枚举化的构建器目标类型
125#[derive(Debug, Clone, Copy)]
126pub enum BuilderKind {
127    /// 网络错误
128    Network,
129    /// 认证错误
130    Authentication,
131    /// API 错误
132    Api,
133    /// 验证错误
134    Validation,
135    /// 配置错误
136    Configuration,
137    /// 序列化错误
138    Serialization,
139    /// 业务错误
140    Business,
141    /// 超时错误
142    Timeout,
143    /// 限流错误
144    RateLimit,
145    /// 服务不可用错误
146    ServiceUnavailable,
147    /// 内部错误
148    Internal,
149}
150
151/// 统一的错误构建器,避免直接依赖枚举内部字段
152#[derive(Debug)]
153pub struct ErrorBuilder {
154    /// 构建器目标类型
155    kind: BuilderKind,
156    /// 错误消息
157    message: Option<String>,
158    /// 错误码
159    code: Option<ErrorCode>,
160    /// 原始错误码(飞书业务码或合成 HTTP status;双域共槽)
161    raw_code: Option<i32>,
162    /// API 端点
163    endpoint: Option<String>,
164    /// 验证字段名
165    field: Option<String>,
166    /// 源错误
167    source: Option<AnyError>,
168    /// 重试策略
169    policy: Option<RetryPolicy>,
170    /// 错误上下文
171    ctx: ErrorContext,
172    /// 持续时间
173    duration: Option<Duration>,
174    /// 操作名
175    operation: Option<String>,
176    /// 限流限制
177    limit: Option<u32>,
178    /// 限流窗口
179    window: Option<Duration>,
180    /// 重置时间
181    reset_after: Option<Duration>,
182    /// 服务名
183    service: Option<String>,
184    /// 重试等待时间
185    retry_after: Option<Duration>,
186}
187
188impl ErrorBuilder {
189    /// 创建指定类型的错误构建器
190    pub fn new(kind: BuilderKind) -> Self {
191        Self {
192            kind,
193            message: None,
194            code: None,
195            raw_code: None,
196            endpoint: None,
197            field: None,
198            source: None,
199            policy: None,
200            ctx: ErrorContext::new(),
201            duration: None,
202            operation: None,
203            limit: None,
204            window: None,
205            reset_after: None,
206            service: None,
207            retry_after: None,
208        }
209    }
210
211    /// 设置错误消息
212    pub fn message(mut self, msg: impl Into<String>) -> Self {
213        self.message = Some(msg.into());
214        self
215    }
216
217    /// 设置错误码
218    pub fn code(mut self, code: ErrorCode) -> Self {
219        self.code = Some(code);
220        self
221    }
222
223    /// 设置原始错误码(飞书业务码或 HTTP status 合成码)
224    pub fn raw_code(mut self, raw_code: i32) -> Self {
225        self.raw_code = Some(raw_code);
226        self
227    }
228
229    /// 设置 API 端点
230    pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
231        self.endpoint = Some(endpoint.into());
232        self
233    }
234
235    /// 设置验证字段名
236    pub fn field(mut self, field: impl Into<String>) -> Self {
237        self.field = Some(field.into());
238        self
239    }
240
241    /// 设置源错误
242    pub fn source<E: std::error::Error + Send + Sync + 'static>(mut self, err: E) -> Self {
243        self.source = Some(Box::new(err));
244        self
245    }
246
247    /// 设置重试策略
248    pub fn policy(mut self, policy: RetryPolicy) -> Self {
249        self.policy = Some(policy);
250        self
251    }
252
253    /// 设置请求 ID
254    pub fn request_id(mut self, req: impl Into<String>) -> Self {
255        self.ctx.set_request_id(req);
256        self
257    }
258
259    /// 设置操作名称
260    pub fn operation(mut self, op: impl Into<String>) -> Self {
261        let op = op.into();
262        self.operation = Some(op.clone());
263        self.ctx.set_operation(op);
264        self
265    }
266
267    /// 设置组件名称
268    pub fn component(mut self, comp: impl Into<String>) -> Self {
269        self.ctx.set_component(comp);
270        self
271    }
272
273    /// 设置用户友好的错误消息
274    pub fn user_message(mut self, msg: impl Into<String>) -> Self {
275        self.ctx.set_user_message(msg);
276        self
277    }
278
279    /// 添加上下文键值对
280    pub fn context(mut self, key: impl Into<String>, val: impl Into<String>) -> Self {
281        self.ctx.add_context(key, val);
282        self
283    }
284
285    /// 设置超时时长
286    pub fn duration(mut self, duration: Duration) -> Self {
287        self.duration = Some(duration);
288        self
289    }
290
291    /// 设置限流次数
292    pub fn limit(mut self, limit: u32) -> Self {
293        self.limit = Some(limit);
294        self
295    }
296
297    /// 设置限流时间窗口
298    pub fn window(mut self, window: Duration) -> Self {
299        self.window = Some(window);
300        self
301    }
302
303    /// 设置限流重置时间
304    pub fn reset_after(mut self, reset: Duration) -> Self {
305        self.reset_after = Some(reset);
306        self
307    }
308
309    /// 设置服务名
310    pub fn service(mut self, service: impl Into<String>) -> Self {
311        self.service = Some(service.into());
312        self
313    }
314
315    /// 设置重试等待时间
316    pub fn retry_after(mut self, retry_after: Duration) -> Self {
317        self.retry_after = Some(retry_after);
318        self
319    }
320
321    /// 构建 CoreError 实例
322    pub fn build(self) -> CoreError {
323        let msg = self.message.unwrap_or_else(|| "unknown error".to_string());
324        match self.kind {
325            BuilderKind::Network => CoreError::Network(Box::new(NetworkError {
326                message: msg,
327                source: self.source,
328                policy: self.policy.unwrap_or_default(),
329                ctx: Box::new(self.ctx),
330            })),
331            BuilderKind::Authentication => CoreError::Authentication {
332                message: msg,
333                code: self.code.unwrap_or(ErrorCode::AuthenticationFailed),
334                ctx: Box::new(self.ctx),
335            },
336            BuilderKind::Api => {
337                let raw_code = self.raw_code.unwrap_or(500);
338                CoreError::Api(Box::new(ApiError {
339                    raw_code,
340                    endpoint: self
341                        .endpoint
342                        .unwrap_or_else(|| "unknown".to_string())
343                        .into(),
344                    message: msg,
345                    source: self.source,
346                    code: self.code.unwrap_or_else(|| ErrorCode::from_code(raw_code)),
347                    ctx: Box::new(self.ctx),
348                }))
349            }
350            BuilderKind::Validation => CoreError::Validation {
351                field: self.field.unwrap_or_else(|| "field".to_string()).into(),
352                message: msg,
353                code: self.code.unwrap_or(ErrorCode::ValidationError),
354                ctx: Box::new(self.ctx),
355            },
356            BuilderKind::Configuration => CoreError::Configuration {
357                message: msg,
358                code: self.code.unwrap_or(ErrorCode::ConfigurationError),
359                ctx: Box::new(self.ctx),
360            },
361            BuilderKind::Serialization => CoreError::Serialization {
362                message: msg,
363                source: self.source,
364                code: self.code.unwrap_or(ErrorCode::SerializationError),
365                ctx: Box::new(self.ctx),
366            },
367            BuilderKind::Business => CoreError::Business {
368                code: self.code.unwrap_or(ErrorCode::BusinessError),
369                message: msg,
370                ctx: Box::new(self.ctx),
371            },
372            BuilderKind::Timeout => CoreError::Timeout {
373                duration: self.duration.unwrap_or_default(),
374                operation: self.operation,
375                ctx: Box::new(self.ctx),
376            },
377            BuilderKind::RateLimit => CoreError::RateLimit {
378                limit: self.limit.unwrap_or(0),
379                window: self.window.unwrap_or(Duration::from_secs(1)),
380                reset_after: self.reset_after,
381                code: self.code.unwrap_or(ErrorCode::RateLimitExceeded),
382                ctx: Box::new(self.ctx),
383            },
384            BuilderKind::ServiceUnavailable => CoreError::ServiceUnavailable {
385                service: self.service.unwrap_or_else(|| "service".to_string()).into(),
386                retry_after: self.retry_after,
387                code: self.code.unwrap_or(ErrorCode::ServiceUnavailable),
388                ctx: Box::new(self.ctx),
389            },
390            BuilderKind::Internal => CoreError::Internal {
391                code: self.code.unwrap_or(ErrorCode::InternalError),
392                message: msg,
393                source: self.source,
394                ctx: Box::new(self.ctx),
395            },
396        }
397    }
398}
399
400/// 轻量版核心错误
401#[non_exhaustive]
402#[derive(Debug, Error)]
403pub enum CoreError {
404    /// 网络错误
405    #[error("网络错误: {0}")]
406    Network(Box<NetworkError>),
407
408    /// 认证错误
409    #[error("认证失败: {message}")]
410    Authentication {
411        /// 错误消息
412        message: String,
413        /// 错误码
414        code: ErrorCode,
415        /// 错误上下文
416        ctx: Box<ErrorContext>,
417    },
418
419    /// API 错误
420    #[error("API错误 {0}")]
421    Api(Box<ApiError>),
422
423    /// 验证错误
424    #[error("验证错误 {field}: {message}")]
425    Validation {
426        /// 字段名
427        field: Cow<'static, str>,
428        /// 错误消息
429        message: String,
430        /// 错误码
431        code: ErrorCode,
432        /// 错误上下文
433        ctx: Box<ErrorContext>,
434    },
435
436    /// 配置错误
437    #[error("配置错误: {message}")]
438    Configuration {
439        /// 错误消息
440        message: String,
441        /// 错误码
442        code: ErrorCode,
443        /// 错误上下文
444        ctx: Box<ErrorContext>,
445    },
446
447    /// 序列化错误
448    #[error("序列化错误: {message}")]
449    Serialization {
450        /// 错误消息
451        message: String,
452        /// 源错误
453        #[source]
454        source: Option<AnyError>,
455        /// 错误码
456        code: ErrorCode,
457        /// 错误上下文
458        ctx: Box<ErrorContext>,
459    },
460
461    /// 业务错误
462    #[error("业务错误 {code:?}: {message}")]
463    Business {
464        /// 错误码
465        code: ErrorCode,
466        /// 错误消息
467        message: String,
468        /// 错误上下文
469        ctx: Box<ErrorContext>,
470    },
471
472    /// 超时错误
473    #[error("超时 {operation:?} after {duration:?}")]
474    Timeout {
475        /// 超时时长
476        duration: Duration,
477        /// 操作名
478        operation: Option<String>,
479        /// 错误上下文
480        ctx: Box<ErrorContext>,
481    },
482
483    /// 限流错误
484    #[error("限流: {limit} 次/{window:?}")]
485    RateLimit {
486        /// 限制次数
487        limit: u32,
488        /// 时间窗口
489        window: Duration,
490        /// 重置时间
491        reset_after: Option<Duration>,
492        /// 错误码
493        code: ErrorCode,
494        /// 错误上下文
495        ctx: Box<ErrorContext>,
496    },
497
498    /// 服务不可用错误
499    #[error("服务不可用: {service}")]
500    ServiceUnavailable {
501        /// 服务名
502        service: Cow<'static, str>,
503        /// 重试等待时间
504        retry_after: Option<Duration>,
505        /// 错误码
506        code: ErrorCode,
507        /// 错误上下文
508        ctx: Box<ErrorContext>,
509    },
510
511    /// 响应体大小超过限制
512    #[error("响应体过大: {actual} 字节超过限制 {limit} 字节")]
513    ResponseTooLarge {
514        /// 配置的大小限制
515        limit: u64,
516        /// 实际大小(已知时),否则为 0
517        actual: u64,
518        /// 错误上下文
519        ctx: Box<ErrorContext>,
520    },
521
522    /// 内部错误
523    #[error("内部错误 {code:?}: {message}")]
524    Internal {
525        /// 错误码
526        code: ErrorCode,
527        /// 错误消息
528        message: String,
529        /// 源错误
530        #[source]
531        source: Option<AnyError>,
532        /// 错误上下文
533        ctx: Box<ErrorContext>,
534    },
535}
536
537/// 网络错误
538#[derive(Debug)]
539pub struct NetworkError {
540    /// 错误消息
541    pub message: String,
542    /// 源错误
543    pub source: Option<AnyError>,
544    /// 重试策略
545    pub policy: RetryPolicy,
546    /// 错误上下文
547    pub ctx: Box<ErrorContext>,
548}
549
550impl std::fmt::Display for NetworkError {
551    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
552        write!(f, "{}", self.message)
553    }
554}
555
556/// API 错误
557#[derive(Debug)]
558pub struct ApiError {
559    /// 原始错误码(飞书业务码或 HTTP 非 2xx 合成 status;与 `RawResponse.code` 同为双域共槽)
560    pub raw_code: i32,
561    /// API 端点
562    pub endpoint: Cow<'static, str>,
563    /// 错误消息
564    pub message: String,
565    /// 源错误
566    pub source: Option<AnyError>,
567    /// 错误码
568    pub code: ErrorCode,
569    /// 错误上下文
570    pub ctx: Box<ErrorContext>,
571}
572
573impl std::fmt::Display for ApiError {
574    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
575        write!(f, "{} {}: {}", self.raw_code, self.endpoint, self.message)
576    }
577}
578
579impl Clone for CoreError {
580    fn clone(&self) -> Self {
581        match self {
582            Self::Network(net) => Self::Network(Box::new(NetworkError {
583                message: net.message.clone(),
584                source: None, // 源错误不可克隆,丢弃以便重试记录
585                policy: net.policy.clone(),
586                ctx: net.ctx.clone(),
587            })),
588            Self::Authentication { message, code, ctx } => Self::Authentication {
589                message: message.clone(),
590                code: *code,
591                ctx: ctx.clone(),
592            },
593            Self::Api(api) => Self::Api(Box::new(ApiError {
594                raw_code: api.raw_code,
595                endpoint: api.endpoint.clone(),
596                message: api.message.clone(),
597                source: None,
598                code: api.code,
599                ctx: api.ctx.clone(),
600            })),
601            Self::Validation {
602                field,
603                message,
604                code,
605                ctx,
606            } => Self::Validation {
607                field: field.clone(),
608                message: message.clone(),
609                code: *code,
610                ctx: ctx.clone(),
611            },
612            Self::Configuration { message, code, ctx } => Self::Configuration {
613                message: message.clone(),
614                code: *code,
615                ctx: ctx.clone(),
616            },
617            Self::Serialization {
618                message, code, ctx, ..
619            } => Self::Serialization {
620                message: message.clone(),
621                source: None,
622                code: *code,
623                ctx: ctx.clone(),
624            },
625            Self::Business { code, message, ctx } => Self::Business {
626                code: *code,
627                message: message.clone(),
628                ctx: ctx.clone(),
629            },
630            Self::Timeout {
631                duration,
632                operation,
633                ctx,
634            } => Self::Timeout {
635                duration: *duration,
636                operation: operation.clone(),
637                ctx: ctx.clone(),
638            },
639            Self::RateLimit {
640                limit,
641                window,
642                reset_after,
643                code,
644                ctx,
645            } => Self::RateLimit {
646                limit: *limit,
647                window: *window,
648                reset_after: *reset_after,
649                code: *code,
650                ctx: ctx.clone(),
651            },
652            Self::ServiceUnavailable {
653                service,
654                retry_after,
655                code,
656                ctx,
657            } => Self::ServiceUnavailable {
658                service: service.clone(),
659                retry_after: *retry_after,
660                code: *code,
661                ctx: ctx.clone(),
662            },
663            Self::ResponseTooLarge { limit, actual, ctx } => Self::ResponseTooLarge {
664                limit: *limit,
665                actual: *actual,
666                ctx: ctx.clone(),
667            },
668            Self::Internal {
669                code, message, ctx, ..
670            } => Self::Internal {
671                code: *code,
672                message: message.clone(),
673                source: None,
674                ctx: ctx.clone(),
675            },
676        }
677    }
678}
679
680impl CoreError {
681    /// 统一构建器入口
682    pub fn builder(kind: BuilderKind) -> ErrorBuilder {
683        ErrorBuilder::new(kind)
684    }
685
686    /// 网络错误构建器
687    pub fn network_builder() -> ErrorBuilder {
688        ErrorBuilder::new(BuilderKind::Network)
689    }
690
691    /// API 错误构建器
692    pub fn api_builder() -> ErrorBuilder {
693        ErrorBuilder::new(BuilderKind::Api)
694    }
695
696    /// 验证错误构建器
697    pub fn validation_builder() -> ErrorBuilder {
698        ErrorBuilder::new(BuilderKind::Validation)
699    }
700
701    /// 认证错误构建器
702    pub fn authentication_builder() -> ErrorBuilder {
703        ErrorBuilder::new(BuilderKind::Authentication)
704    }
705
706    /// 业务错误构建器
707    pub fn business_builder() -> ErrorBuilder {
708        ErrorBuilder::new(BuilderKind::Business)
709    }
710
711    /// 简单网络错误(无 source)
712    pub fn network_msg(message: impl Into<String>) -> Self {
713        network_error(message)
714    }
715
716    /// 简单认证错误
717    pub fn authentication(message: impl Into<String>) -> Self {
718        authentication_error(message)
719    }
720
721    /// 简单 API 错误(便于兼容旧 CoreError::api_error)
722    pub fn api_error(
723        raw_code: i32,
724        endpoint: impl Into<String>,
725        message: impl Into<String>,
726        request_id: Option<impl Into<String>>,
727    ) -> Self {
728        api_error(raw_code, endpoint, message, request_id.map(|id| id.into()))
729    }
730
731    /// 仅带 message 的验证错误(默认字段 general)
732    pub fn validation_msg(message: impl Into<String>) -> Self {
733        validation_error("general", message)
734    }
735
736    /// 用户可读 message(兼容旧 API)
737    pub fn message(&self) -> String {
738        self.to_string()
739    }
740
741    /// 错误类型别名(兼容旧 kind)
742    pub fn kind(&self) -> ErrorType {
743        self.error_type()
744    }
745
746    /// 直接判断 API 错误(便捷别名)
747    pub fn is_api_error(&self) -> bool {
748        matches!(self, Self::Api(_))
749    }
750
751    // === 兼容旧 CoreError 的工厂 ===
752    /// 验证错误
753    pub fn validation(field: impl Into<String>, message: impl Into<String>) -> Self {
754        let mut ctx = ErrorContext::new();
755        let field = field.into();
756        ctx.add_context("field", field.clone());
757        Self::Validation {
758            field: field.into(),
759            message: message.into(),
760            code: ErrorCode::ValidationError,
761            ctx: Box::new(ctx),
762        }
763    }
764
765    /// API 数据错误
766    pub fn api_data_error(message: impl Into<String>) -> Self {
767        Self::Api(Box::new(ApiError {
768            raw_code: 500,
769            endpoint: "data_error".into(),
770            message: format!("no data: {}", message.into()),
771            source: None,
772            code: ErrorCode::InternalServerError,
773            ctx: Box::new(ErrorContext::new()),
774        }))
775    }
776
777    /// 获取错误码
778    pub fn code(&self) -> ErrorCode {
779        match self {
780            Self::Network(_) => ErrorCode::NetworkConnectionFailed,
781            Self::Authentication { code, .. } => *code,
782            Self::Api(api) => api.code,
783            Self::Validation { code, .. } => *code,
784            Self::Configuration { code, .. } => *code,
785            Self::Serialization { code, .. } => *code,
786            Self::Business { code, .. } => *code,
787            Self::Timeout { .. } => ErrorCode::NetworkTimeout,
788            Self::RateLimit { code, .. } => *code,
789            Self::ServiceUnavailable { code, .. } => *code,
790            Self::ResponseTooLarge { .. } => ErrorCode::ResponseTooLarge,
791            Self::Internal { code, .. } => *code,
792        }
793    }
794
795    /// 获取错误严重程度
796    pub fn severity(&self) -> ErrorSeverity {
797        self.code().severity()
798    }
799
800    /// 是否可重试
801    ///
802    /// `Network` 走 policy(可配置 max_retries);其余与分类共用
803    /// [`ErrorCode::is_retryable`](#545:Api 不再按 raw_code 数值范围)。
804    pub fn is_retryable(&self) -> bool {
805        match self {
806            Self::Network(net) => net.policy.is_retryable(),
807            _ => self.code().is_retryable(),
808        }
809    }
810
811    /// 获取重试延迟
812    pub fn retry_delay(&self, attempt: u32) -> Option<Duration> {
813        match self {
814            Self::Network(net) => net.policy.retry_delay(attempt),
815            Self::RateLimit { window, .. } => Some(*window),
816            Self::ServiceUnavailable { retry_after, .. } => *retry_after,
817            // #545:谓词切 ErrorCode variant;延迟公式保持 `1 << attempt.min(5)`
818            // (不用 suggested_retry_delay,避免 429 固定 60s 破坏等价回归)
819            Self::Api(_) if self.code().is_retryable() => {
820                Some(Duration::from_secs(1 << attempt.min(5)))
821            }
822            _ => None,
823        }
824    }
825
826    /// 获取错误上下文
827    pub fn ctx(&self) -> &ErrorContext {
828        match self {
829            Self::Network(net) => &net.ctx,
830            Self::Api(api) => &api.ctx,
831            Self::Authentication { ctx, .. }
832            | Self::Validation { ctx, .. }
833            | Self::Configuration { ctx, .. }
834            | Self::Serialization { ctx, .. }
835            | Self::Business { ctx, .. }
836            | Self::Timeout { ctx, .. }
837            | Self::RateLimit { ctx, .. }
838            | Self::ServiceUnavailable { ctx, .. }
839            | Self::ResponseTooLarge { ctx, .. }
840            | Self::Internal { ctx, .. } => ctx,
841        }
842    }
843
844    /// 修改错误上下文
845    pub fn map_context(self, f: impl FnOnce(&mut ErrorContext)) -> Self {
846        match self {
847            Self::Network(mut net) => {
848                f(net.ctx.as_mut());
849                Self::Network(net)
850            }
851            Self::Authentication {
852                message,
853                code,
854                mut ctx,
855            } => {
856                f(ctx.as_mut());
857                Self::Authentication { message, code, ctx }
858            }
859            Self::Api(mut api) => {
860                f(api.ctx.as_mut());
861                Self::Api(api)
862            }
863            Self::Validation {
864                field,
865                message,
866                code,
867                mut ctx,
868            } => {
869                f(ctx.as_mut());
870                Self::Validation {
871                    field,
872                    message,
873                    code,
874                    ctx,
875                }
876            }
877            Self::Configuration {
878                message,
879                code,
880                mut ctx,
881            } => {
882                f(ctx.as_mut());
883                Self::Configuration { message, code, ctx }
884            }
885            Self::Serialization {
886                message,
887                source,
888                code,
889                mut ctx,
890            } => {
891                f(ctx.as_mut());
892                Self::Serialization {
893                    message,
894                    source,
895                    code,
896                    ctx,
897                }
898            }
899            Self::Business {
900                code,
901                message,
902                mut ctx,
903            } => {
904                f(ctx.as_mut());
905                Self::Business { code, message, ctx }
906            }
907            Self::Timeout {
908                duration,
909                operation,
910                mut ctx,
911            } => {
912                f(ctx.as_mut());
913                Self::Timeout {
914                    duration,
915                    operation,
916                    ctx,
917                }
918            }
919            Self::RateLimit {
920                limit,
921                window,
922                reset_after,
923                code,
924                mut ctx,
925            } => {
926                f(ctx.as_mut());
927                Self::RateLimit {
928                    limit,
929                    window,
930                    reset_after,
931                    code,
932                    ctx,
933                }
934            }
935            Self::ServiceUnavailable {
936                service,
937                retry_after,
938                code,
939                mut ctx,
940            } => {
941                f(ctx.as_mut());
942                Self::ServiceUnavailable {
943                    service,
944                    retry_after,
945                    code,
946                    ctx,
947                }
948            }
949            Self::ResponseTooLarge {
950                limit,
951                actual,
952                mut ctx,
953            } => {
954                f(ctx.as_mut());
955                Self::ResponseTooLarge { limit, actual, ctx }
956            }
957            Self::Internal {
958                code,
959                message,
960                source,
961                mut ctx,
962            } => {
963                f(ctx.as_mut());
964                Self::Internal {
965                    code,
966                    message,
967                    source,
968                    ctx,
969                }
970            }
971        }
972    }
973
974    /// 添加上下文键值对
975    pub fn with_context_kv(self, key: impl Into<String>, value: impl Into<String>) -> Self {
976        let key = key.into();
977        let value = value.into();
978        self.map_context(|ctx| {
979            ctx.add_context(key, value);
980        })
981    }
982
983    /// 附加请求 ID,上下游都使用统一字段传播链路标识。
984    pub fn with_request_id(self, request_id: impl Into<String>) -> Self {
985        let request_id = request_id.into();
986        self.map_context(|ctx| {
987            ctx.set_request_id(request_id);
988        })
989    }
990
991    /// 附加资源上下文,统一记录当前错误所属的业务资源/动作描述。
992    pub fn with_resource(self, resource: impl Into<String>) -> Self {
993        let resource = resource.into();
994        self.map_context(|ctx| {
995            ctx.add_context("resource", resource);
996        })
997    }
998
999    /// 添加操作和组件信息
1000    pub fn with_operation(
1001        self,
1002        operation: impl Into<String>,
1003        component: impl Into<String>,
1004    ) -> Self {
1005        let operation = operation.into();
1006        let component = component.into();
1007
1008        let mapped = self.map_context(|ctx| {
1009            ctx.set_operation(operation.clone())
1010                .set_component(component.clone())
1011                .add_context("operation", operation.clone())
1012                .add_context("component", component.clone());
1013        });
1014
1015        match mapped {
1016            Self::Timeout { duration, ctx, .. } => Self::Timeout {
1017                duration,
1018                operation: Some(operation),
1019                ctx,
1020            },
1021            other => other,
1022        }
1023    }
1024
1025    /// 一次性附加标准错误上下文字段:operation / component / resource / request_id。
1026    pub fn with_standard_context(
1027        self,
1028        operation: impl Into<String>,
1029        component: impl Into<String>,
1030        resource: impl Into<String>,
1031        request_id: Option<String>,
1032    ) -> Self {
1033        let mut err = self
1034            .with_operation(operation, component)
1035            .with_resource(resource);
1036
1037        if let Some(request_id) = request_id.filter(|value| !value.trim().is_empty()) {
1038            err = err.with_request_id(request_id);
1039        }
1040
1041        err
1042    }
1043
1044    /// 观测记录(可序列化)——供日志/指标/告警统一使用
1045    pub fn record(&self) -> ErrorRecord {
1046        ErrorRecord::from(self)
1047    }
1048
1049    // === 快速构造器(示例) ===
1050    /// 网络错误
1051    pub fn network<E>(source: E, ctx: ErrorContext) -> Self
1052    where
1053        E: std::error::Error + Send + Sync + 'static,
1054    {
1055        Self::Network(Box::new(NetworkError {
1056            message: "网络连接失败".to_string(),
1057            source: Some(Box::new(source)),
1058            policy: RetryPolicy::default(),
1059            ctx: Box::new(ctx),
1060        }))
1061    }
1062
1063    /// API 错误
1064    pub fn api(
1065        raw_code: i32,
1066        endpoint: impl Into<Cow<'static, str>>,
1067        message: impl Into<String>,
1068        ctx: ErrorContext,
1069    ) -> Self {
1070        Self::Api(Box::new(ApiError {
1071            raw_code,
1072            endpoint: endpoint.into(),
1073            message: message.into(),
1074            source: None,
1075            code: ErrorCode::from_code(raw_code),
1076            ctx: Box::new(ctx),
1077        }))
1078    }
1079
1080    /// 响应体过大错误
1081    pub fn response_too_large(limit: u64, actual: u64) -> Self {
1082        Self::ResponseTooLarge {
1083            limit,
1084            actual,
1085            ctx: Box::new(ErrorContext::new()),
1086        }
1087    }
1088}
1089
1090/// 统一的可观测记录
1091#[derive(Debug, Serialize)]
1092#[serde_with::skip_serializing_none]
1093pub struct ErrorRecord {
1094    /// 错误码
1095    pub code: ErrorCode,
1096    /// 严重程度
1097    pub severity: ErrorSeverity,
1098    /// 是否可重试
1099    pub retryable: bool,
1100    /// 重试延迟(毫秒)
1101    pub retry_delay_ms: Option<u64>,
1102    /// 错误消息
1103    pub message: String,
1104    /// 上下文
1105    pub context: std::collections::HashMap<String, String>,
1106    /// 请求 ID
1107    pub request_id: Option<String>,
1108    /// 操作名称
1109    pub operation: Option<String>,
1110    /// 组件名称
1111    pub component: Option<String>,
1112}
1113
1114impl From<&CoreError> for ErrorRecord {
1115    fn from(err: &CoreError) -> Self {
1116        let ctx = err.ctx();
1117        Self {
1118            code: err.code(),
1119            severity: err.severity(),
1120            retryable: err.is_retryable(),
1121            retry_delay_ms: err.retry_delay(0).map(|d| d.as_millis() as u64),
1122            message: err.to_string(),
1123            context: ctx.all_context().clone(),
1124            request_id: ctx.request_id().map(|s| s.to_string()),
1125            operation: ctx.operation().map(|s| s.to_string()),
1126            component: ctx.component().map(|s| s.to_string()),
1127        }
1128    }
1129}
1130
1131impl From<reqwest::Error> for CoreError {
1132    fn from(source: reqwest::Error) -> Self {
1133        Self::Network(Box::new(NetworkError {
1134            message: source.to_string(),
1135            source: Some(Box::new(source)),
1136            policy: RetryPolicy::default(),
1137            ctx: Box::new(ErrorContext::new()),
1138        }))
1139    }
1140}
1141
1142impl From<serde_json::Error> for CoreError {
1143    fn from(source: serde_json::Error) -> Self {
1144        Self::Serialization {
1145            message: format!("JSON序列化错误: {source}"),
1146            source: Some(Box::new(source)),
1147            code: ErrorCode::SerializationError,
1148            ctx: Box::new(ErrorContext::new()),
1149        }
1150    }
1151}
1152
1153impl ErrorTrait for CoreError {
1154    fn severity(&self) -> ErrorSeverity {
1155        self.severity()
1156    }
1157
1158    fn is_retryable(&self) -> bool {
1159        self.is_retryable()
1160    }
1161
1162    fn retry_delay(&self, attempt: u32) -> Option<Duration> {
1163        self.retry_delay(attempt)
1164    }
1165
1166    fn user_message(&self) -> Option<&str> {
1167        match self {
1168            Self::Network(_) => Some("网络连接异常,请稍后重试"),
1169            Self::Authentication { .. } => Some("认证失败,请重新登录"),
1170            Self::Api(api) => Some(api.message.as_str()),
1171            Self::Validation { message, .. } => Some(message.as_str()),
1172            Self::Configuration { message, .. } => Some(message.as_str()),
1173            Self::Serialization { message, .. } => Some(message.as_str()),
1174            Self::Business { message, .. } => Some(message.as_str()),
1175            Self::Timeout { .. } => Some("请求超时,请稍后重试"),
1176            Self::RateLimit { .. } => Some("请求过于频繁,请稍候"),
1177            Self::ServiceUnavailable { .. } => Some("服务暂不可用,请稍后重试"),
1178            Self::ResponseTooLarge { .. } => Some("响应数据过大,请减小请求范围"),
1179            Self::Internal { message, .. } => Some(message.as_str()),
1180        }
1181    }
1182
1183    fn context(&self) -> &ErrorContext {
1184        self.ctx()
1185    }
1186
1187    fn error_type(&self) -> ErrorType {
1188        match self {
1189            Self::Network(_) => ErrorType::Network,
1190            Self::Authentication { .. } => ErrorType::Authentication,
1191            Self::Api(_) => ErrorType::Api,
1192            Self::Validation { .. } => ErrorType::Validation,
1193            Self::Configuration { .. } => ErrorType::Configuration,
1194            Self::Serialization { .. } => ErrorType::Serialization,
1195            Self::Business { .. } => ErrorType::Business,
1196            Self::Timeout { .. } => ErrorType::Timeout,
1197            Self::RateLimit { .. } => ErrorType::RateLimit,
1198            Self::ServiceUnavailable { .. } => ErrorType::ServiceUnavailable,
1199            Self::ResponseTooLarge { .. } => ErrorType::ResponseTooLarge,
1200            Self::Internal { .. } => ErrorType::Internal,
1201        }
1202    }
1203
1204    fn error_code(&self) -> Option<&str> {
1205        None
1206    }
1207}
1208
1209// 便利函数(保持向后兼容)
1210
1211/// 创建网络错误
1212pub fn network_error(message: impl Into<String>) -> CoreError {
1213    CoreError::Network(Box::new(NetworkError {
1214        message: message.into(),
1215        source: None,
1216        policy: RetryPolicy::default(),
1217        ctx: Box::new(ErrorContext::new()),
1218    }))
1219}
1220
1221/// 创建认证错误
1222pub fn authentication_error(message: impl Into<String>) -> CoreError {
1223    CoreError::Authentication {
1224        message: message.into(),
1225        code: ErrorCode::AuthenticationFailed,
1226        ctx: Box::new(ErrorContext::new()),
1227    }
1228}
1229
1230/// 创建API错误
1231///
1232/// `raw_code` 为原始错误码(飞书业务码或 HTTP 合成 status),经
1233/// [`ErrorCode::from_code`] 不截断分类。
1234pub fn api_error(
1235    raw_code: i32,
1236    endpoint: impl Into<String>,
1237    message: impl Into<String>,
1238    request_id: Option<String>,
1239) -> CoreError {
1240    CoreError::Api(Box::new(ApiError {
1241        raw_code,
1242        endpoint: endpoint.into().into(),
1243        message: message.into(),
1244        source: None,
1245        code: ErrorCode::from_code(raw_code),
1246        ctx: {
1247            let mut ctx = ErrorContext::new();
1248            if let Some(req_id) = request_id {
1249                ctx.set_request_id(req_id);
1250            }
1251            Box::new(ctx)
1252        },
1253    }))
1254}
1255
1256/// 创建验证错误
1257pub fn validation_error(field: impl Into<String>, message: impl Into<String>) -> CoreError {
1258    let field = field.into();
1259    let mut ctx = ErrorContext::new();
1260    ctx.add_context("field", field.clone());
1261
1262    CoreError::Validation {
1263        field: field.into(),
1264        message: message.into(),
1265        code: ErrorCode::ValidationError,
1266        ctx: Box::new(ctx),
1267    }
1268}
1269
1270/// 创建序列化错误
1271pub fn serialization_error<T: std::error::Error + Send + Sync + 'static>(
1272    message: impl Into<String>,
1273    source: Option<T>,
1274) -> CoreError {
1275    CoreError::Serialization {
1276        message: message.into(),
1277        source: source.map(|e| Box::new(e) as AnyError),
1278        code: ErrorCode::SerializationError,
1279        ctx: Box::new(ErrorContext::new()),
1280    }
1281}
1282
1283/// 创建业务错误
1284pub fn business_error(message: impl Into<String>) -> CoreError {
1285    CoreError::Business {
1286        message: message.into(),
1287        code: ErrorCode::BusinessError,
1288        ctx: Box::new(ErrorContext::new()),
1289    }
1290}
1291
1292/// 创建配置错误
1293pub fn configuration_error(message: impl Into<String>) -> CoreError {
1294    CoreError::Configuration {
1295        message: message.into(),
1296        code: ErrorCode::ConfigurationError,
1297        ctx: Box::new(ErrorContext::new()),
1298    }
1299}
1300
1301/// 创建超时错误
1302pub fn timeout_error(timeout: Duration, operation: Option<String>) -> CoreError {
1303    CoreError::Timeout {
1304        duration: timeout,
1305        operation,
1306        ctx: Box::new(ErrorContext::new()),
1307    }
1308}
1309
1310/// 创建限流错误
1311pub fn rate_limit_error(limit: u32, window: Duration, retry_after: Option<Duration>) -> CoreError {
1312    CoreError::RateLimit {
1313        limit,
1314        window,
1315        reset_after: retry_after,
1316        code: ErrorCode::TooManyRequests,
1317        ctx: Box::new(ErrorContext::new()),
1318    }
1319}
1320
1321/// 创建服务不可用错误
1322pub fn service_unavailable_error(
1323    service: impl Into<String>,
1324    retry_after: Option<Duration>,
1325) -> CoreError {
1326    CoreError::ServiceUnavailable {
1327        service: service.into().into(),
1328        retry_after,
1329        code: ErrorCode::ServiceUnavailable,
1330        ctx: Box::new(ErrorContext::new()),
1331    }
1332}
1333
1334/// 创建权限缺失错误
1335pub fn permission_missing_error(scopes: &[impl AsRef<str>]) -> CoreError {
1336    let mut ctx = ErrorContext::new();
1337    ctx.add_context(
1338        "required_scopes",
1339        scopes
1340            .iter()
1341            .map(|s| s.as_ref())
1342            .collect::<Vec<_>>()
1343            .join(","),
1344    );
1345
1346    CoreError::Authentication {
1347        message: "权限范围不足".to_string(),
1348        code: ErrorCode::PermissionMissing,
1349        ctx: Box::new(ctx),
1350    }
1351}
1352
1353/// 创建SSO令牌无效错误
1354pub fn sso_token_invalid_error(detail: impl Into<String>) -> CoreError {
1355    let mut ctx = ErrorContext::new();
1356    ctx.add_context("detail", detail.into());
1357
1358    CoreError::Authentication {
1359        message: "SSO令牌无效".to_string(),
1360        code: ErrorCode::SsoTokenInvalid,
1361        ctx: Box::new(ctx),
1362    }
1363}
1364
1365/// 创建用户身份无效错误
1366pub fn user_identity_invalid_error(desc: impl Into<String>) -> CoreError {
1367    let mut ctx = ErrorContext::new();
1368    ctx.add_context("description", desc.into());
1369
1370    CoreError::Authentication {
1371        message: "用户身份无效".to_string(),
1372        code: ErrorCode::UserIdentityInvalid,
1373        ctx: Box::new(ctx),
1374    }
1375}
1376
1377/// 创建访问令牌无效错误
1378pub fn token_invalid_error(detail: impl Into<String>) -> CoreError {
1379    let mut ctx = ErrorContext::new();
1380    ctx.add_context("detail", detail.into());
1381
1382    CoreError::Authentication {
1383        message: "访问令牌无效".to_string(),
1384        code: ErrorCode::AccessTokenInvalid,
1385        ctx: Box::new(ctx),
1386    }
1387}
1388
1389/// 创建访问令牌过期错误
1390pub fn token_expired_error(detail: impl Into<String>) -> CoreError {
1391    let mut ctx = ErrorContext::new();
1392    ctx.add_context("detail", detail.into());
1393
1394    CoreError::Authentication {
1395        message: "访问令牌过期".to_string(),
1396        code: ErrorCode::AccessTokenExpiredV2,
1397        ctx: Box::new(ctx),
1398    }
1399}
1400
1401/// 创建带详细信息的网络错误
1402pub fn network_error_with_details(
1403    message: impl Into<String>,
1404    endpoint: Option<String>,
1405    request_id: Option<String>,
1406) -> CoreError {
1407    let mut ctx = ErrorContext::new();
1408    if let Some(ep) = endpoint {
1409        ctx.add_context("endpoint", ep);
1410    }
1411    if let Some(req_id) = request_id {
1412        ctx.set_request_id(req_id);
1413    }
1414
1415    CoreError::Network(Box::new(NetworkError {
1416        message: message.into(),
1417        source: None,
1418        policy: RetryPolicy::default(),
1419        ctx: Box::new(ctx),
1420    }))
1421}
1422
1423#[cfg(test)]
1424mod tests {
1425    use super::*;
1426    use std::time::Duration;
1427
1428    #[test]
1429    fn api_error_has_code_and_severity() {
1430        let err = CoreError::api(503, "/ping", "service down", ErrorContext::new());
1431
1432        assert_eq!(err.code(), ErrorCode::ServiceUnavailable);
1433        assert!(err.is_retryable());
1434        assert_eq!(err.severity(), ErrorSeverity::Critical);
1435        assert!(err.retry_delay(1).is_some());
1436    }
1437
1438    #[test]
1439    fn record_contains_context() {
1440        let mut ctx = ErrorContext::new();
1441        ctx.add_context("endpoint", "/user");
1442        ctx.set_request_id("req-1");
1443
1444        let err = CoreError::network(std::io::Error::other("boom"), ctx);
1445
1446        let rec = err.record();
1447        assert_eq!(rec.code, ErrorCode::NetworkConnectionFailed);
1448        assert_eq!(rec.context.get("endpoint"), Some(&"/user".to_string()));
1449        assert_eq!(rec.request_id.as_deref(), Some("req-1"));
1450    }
1451
1452    #[test]
1453    fn core_error_to_record() {
1454        let err = CoreError::api(503, "/ping", "svc down", ErrorContext::new());
1455        let rec: ErrorRecord = (&err).into();
1456        assert_eq!(rec.code, ErrorCode::ServiceUnavailable);
1457        assert!(rec.retryable);
1458        assert!(rec.message.contains("API错误"));
1459    }
1460
1461    #[test]
1462    fn builder_creates_api_error_with_context() {
1463        let err = CoreError::api_builder()
1464            .raw_code(404)
1465            .endpoint("/users/1")
1466            .message("not found")
1467            .request_id("req-123")
1468            .build();
1469
1470        assert!(err.is_api_error());
1471        assert_eq!(err.context().request_id(), Some("req-123"));
1472        assert_eq!(err.code(), ErrorCode::NotFound);
1473    }
1474
1475    #[test]
1476    fn rate_limit_retry_delay() {
1477        let err = CoreError::RateLimit {
1478            limit: 10,
1479            window: Duration::from_secs(60),
1480            reset_after: Some(Duration::from_secs(30)),
1481            code: ErrorCode::RateLimitExceeded,
1482            ctx: Box::new(ErrorContext::new()),
1483        };
1484
1485        assert!(err.is_retryable());
1486        assert_eq!(err.retry_delay(0), Some(Duration::from_secs(60)));
1487    }
1488
1489    #[test]
1490    fn from_reqwest_error() {
1491        // 无法直接构造 reqwest::Error(构造函数为私有),跳过具体实例化,只验证 From trait 存在
1492        fn assert_from_reqwest<E: Into<CoreError>>() {}
1493        assert_from_reqwest::<reqwest::Error>();
1494    }
1495
1496    #[test]
1497    fn map_context_covers_all_variants() {
1498        let errors = vec![
1499            network_error("n"),
1500            authentication_error("a"),
1501            api_error(500, "/api", "api", None),
1502            validation_error("field", "invalid"),
1503            configuration_error("cfg"),
1504            serialization_error("serde", None::<serde_json::Error>),
1505            business_error("biz"),
1506            timeout_error(Duration::from_secs(1), None),
1507            rate_limit_error(100, Duration::from_secs(60), Some(Duration::from_secs(10))),
1508            service_unavailable_error("svc", Some(Duration::from_secs(30))),
1509            CoreError::response_too_large(1024 * 1024, 5 * 1024 * 1024),
1510            CoreError::Internal {
1511                code: ErrorCode::InternalError,
1512                message: "internal".to_string(),
1513                source: None,
1514                ctx: Box::new(ErrorContext::new()),
1515            },
1516        ];
1517
1518        for err in errors {
1519            let updated = err.map_context(|ctx| {
1520                ctx.add_context("k", "v");
1521            });
1522            assert_eq!(updated.context().get_context("k"), Some("v"));
1523        }
1524    }
1525
1526    #[test]
1527    fn with_context_kv_adds_context() {
1528        let err = validation_error("field", "invalid").with_context_kv("user_id", "u-1");
1529        assert_eq!(err.context().get_context("user_id"), Some("u-1"));
1530    }
1531
1532    #[test]
1533    fn with_request_id_updates_context() {
1534        let err = validation_error("field", "invalid").with_request_id("req-123");
1535        assert_eq!(err.context().request_id(), Some("req-123"));
1536    }
1537
1538    #[test]
1539    fn with_resource_adds_resource_context() {
1540        let err = validation_error("field", "invalid").with_resource("查询记录");
1541        assert_eq!(err.context().get_context("resource"), Some("查询记录"));
1542    }
1543
1544    #[test]
1545    fn with_operation_updates_timeout_field_and_context() {
1546        let err = timeout_error(Duration::from_secs(30), Some("old_op".to_string()))
1547            .with_operation("new_op", "client");
1548
1549        match err {
1550            CoreError::Timeout {
1551                operation, ref ctx, ..
1552            } => {
1553                assert_eq!(operation.as_deref(), Some("new_op"));
1554                assert_eq!(ctx.operation(), Some("new_op"));
1555                assert_eq!(ctx.component(), Some("client"));
1556                assert_eq!(ctx.get_context("operation"), Some("new_op"));
1557                assert_eq!(ctx.get_context("component"), Some("client"));
1558            }
1559            other => panic!("expected timeout error, got {:?}", other.error_type()),
1560        }
1561    }
1562
1563    #[test]
1564    fn with_standard_context_sets_all_standard_fields() {
1565        let err = validation_error("response.data", "响应数据为空").with_standard_context(
1566            "extract_response_data",
1567            "openlark-docs",
1568            "查询记录",
1569            Some("req-456".to_string()),
1570        );
1571
1572        assert_eq!(err.context().operation(), Some("extract_response_data"));
1573        assert_eq!(err.context().component(), Some("openlark-docs"));
1574        assert_eq!(err.context().get_context("resource"), Some("查询记录"));
1575        assert_eq!(err.context().request_id(), Some("req-456"));
1576    }
1577
1578    /// #545:Api 重试判定走 ErrorCode variant;延迟公式保持 `1 << attempt.min(5)`。
1579    #[test]
1580    fn api_retry_uses_errorcode_variant_with_stable_delay_formula() {
1581        // 合成 HTTP 429 / 5xx 族 → 可重试(from_code 映射到五个 variant)
1582        for (raw, expected) in [
1583            (429, ErrorCode::TooManyRequests),
1584            (500, ErrorCode::InternalServerError),
1585            (502, ErrorCode::BadGateway),
1586            (503, ErrorCode::ServiceUnavailable),
1587            (504, ErrorCode::GatewayTimeout),
1588        ] {
1589            let err = api_error(raw, "/api", "retryable", None::<String>);
1590            assert_eq!(err.code(), expected, "raw_code={raw}");
1591            assert!(
1592                err.is_retryable(),
1593                "{expected:?} (raw={raw}) must be retryable via code.is_retryable()"
1594            );
1595            // 不换成 suggested_retry_delay(429 固定 60s);保持 1<<attempt
1596            assert_eq!(err.retry_delay(0), Some(Duration::from_secs(1)));
1597            assert_eq!(err.retry_delay(1), Some(Duration::from_secs(2)));
1598            assert_eq!(err.retry_delay(5), Some(Duration::from_secs(32)));
1599            assert_eq!(
1600                err.retry_delay(6),
1601                Some(Duration::from_secs(32)),
1602                "attempt.min(5) caps shift"
1603            );
1604        }
1605
1606        // 飞书业务码 → 分类正确且不可重试
1607        let feishu = api_error(99991663, "/api", "token invalid", None::<String>);
1608        assert_eq!(feishu.code(), ErrorCode::TenantAccessTokenInvalid);
1609        assert!(!feishu.is_retryable());
1610        assert!(feishu.retry_delay(0).is_none());
1611
1612        // 4xx 客户端错误不可重试
1613        let bad = api_error(400, "/api", "bad request", None::<String>);
1614        assert!(!bad.is_retryable());
1615        assert!(bad.retry_delay(0).is_none());
1616    }
1617}