open_lark/core/
error_helper.rs

1use std::time::Duration;
2
3use crate::core::{
4    api_resp::BaseResponse,
5    error::LarkAPIError,
6    error_codes::{ErrorCategory, LarkErrorCode},
7};
8
9/// 错误处理助手工具
10pub struct ErrorHelper;
11
12impl ErrorHelper {
13    /// 根据错误类型提供智能处理建议
14    pub fn handle_error(error: &LarkAPIError) -> ErrorHandlingAdvice {
15        let mut advice = ErrorHandlingAdvice::default();
16
17        match error {
18            LarkAPIError::ApiError { code, message, .. } => {
19                if let Some(error_code) = LarkErrorCode::from_code(*code) {
20                    advice = Self::handle_api_error(error_code, message);
21                } else {
22                    advice.message = format!("未知API错误: {message} (错误码: {code})");
23                    advice.category = ErrorHandlingCategory::Unknown;
24                }
25            }
26            LarkAPIError::RequestError(req_err) => {
27                advice = Self::handle_request_error(req_err);
28            }
29            LarkAPIError::MissingAccessToken => {
30                advice.message = "缺少访问令牌".to_string();
31                advice.category = ErrorHandlingCategory::Authentication;
32                advice.actions.push("配置正确的访问令牌".to_string());
33                advice.is_recoverable = true;
34            }
35            LarkAPIError::IllegalParamError(msg) => {
36                advice.message = format!("参数错误: {msg}");
37                advice.category = ErrorHandlingCategory::ClientError;
38                advice.actions.push("检查请求参数格式和内容".to_string());
39                advice.is_recoverable = true;
40            }
41            _ => {
42                advice.message = format!("系统错误: {error}");
43                advice.category = ErrorHandlingCategory::SystemError;
44            }
45        }
46
47        advice
48    }
49
50    /// 处理API错误
51    fn handle_api_error(error_code: LarkErrorCode, _message: &str) -> ErrorHandlingAdvice {
52        let mut advice = ErrorHandlingAdvice {
53            error_code: Some(error_code),
54            message: error_code.detailed_description().to_string(),
55            ..Default::default()
56        };
57
58        match error_code.category() {
59            ErrorCategory::Authentication => {
60                advice.category = ErrorHandlingCategory::Authentication;
61                advice.is_recoverable = true;
62                advice.actions.extend(vec![
63                    "重新获取访问令牌".to_string(),
64                    "检查应用配置".to_string(),
65                ]);
66            }
67            ErrorCategory::Permission => {
68                advice.category = ErrorHandlingCategory::Permission;
69                advice.is_recoverable = true;
70                advice.actions.extend(vec![
71                    "检查应用权限配置".to_string(),
72                    "联系管理员添加必要权限".to_string(),
73                ]);
74            }
75            ErrorCategory::RateLimit => {
76                advice.category = ErrorHandlingCategory::RateLimit;
77                advice.is_recoverable = true;
78                advice.is_retryable = true;
79                advice.retry_delay = error_code.suggested_retry_delay();
80                advice.actions.push("降低请求频率或稍后重试".to_string());
81            }
82            ErrorCategory::Server => {
83                advice.category = ErrorHandlingCategory::ServerError;
84                advice.is_recoverable = true;
85                advice.is_retryable = true;
86                advice.retry_delay = error_code.suggested_retry_delay();
87                advice.actions.push("稍后重试或联系技术支持".to_string());
88            }
89            ErrorCategory::Network => {
90                advice.category = ErrorHandlingCategory::NetworkError;
91                advice.is_recoverable = true;
92                advice.is_retryable = true;
93                advice.actions.push("检查网络连接".to_string());
94            }
95            _ => {
96                advice.category = ErrorHandlingCategory::ClientError;
97                advice.actions.push("检查请求参数和调用方式".to_string());
98            }
99        }
100
101        if let Some(help_url) = error_code.help_url() {
102            advice.help_url = Some(help_url.to_string());
103        }
104
105        advice
106    }
107
108    /// 处理网络请求错误
109    fn handle_request_error(req_err: &str) -> ErrorHandlingAdvice {
110        let mut advice = ErrorHandlingAdvice {
111            category: ErrorHandlingCategory::NetworkError,
112            is_recoverable: true,
113            ..Default::default()
114        };
115
116        if req_err.contains("timeout") || req_err.contains("timed out") {
117            advice.message = "请求超时".to_string();
118            advice.is_retryable = true;
119            advice.retry_delay = Some(5);
120            advice.actions.extend(vec![
121                "增加请求超时时间".to_string(),
122                "检查网络连接状况".to_string(),
123            ]);
124        } else if req_err.contains("connect") || req_err.contains("connection") {
125            advice.message = "连接失败".to_string();
126            advice.is_retryable = true;
127            advice.retry_delay = Some(10);
128            advice.actions.extend(vec![
129                "检查网络连接".to_string(),
130                "确认代理设置".to_string(),
131                "检查防火墙配置".to_string(),
132            ]);
133        } else if req_err.contains("request") {
134            advice.message = "请求构建失败".to_string();
135            advice.actions.push("检查请求参数格式".to_string());
136        } else {
137            advice.message = format!("网络错误: {req_err}");
138            advice.actions.push("检查网络连接和服务状态".to_string());
139        }
140
141        advice
142    }
143
144    /// 根据响应创建处理建议
145    pub fn analyze_response<T>(response: &BaseResponse<T>) -> Option<ErrorHandlingAdvice> {
146        if response.success() {
147            return None;
148        }
149
150        let mut advice = ErrorHandlingAdvice::default();
151
152        if let Some(error_code) = response.error_code() {
153            advice = Self::handle_api_error(error_code, response.msg());
154        } else {
155            advice.message = format!("{} (错误码: {})", response.msg(), response.code());
156            advice.category = ErrorHandlingCategory::Unknown;
157        }
158
159        Some(advice)
160    }
161
162    /// 创建重试策略
163    pub fn create_retry_strategy(error: &LarkAPIError) -> Option<RetryStrategy> {
164        if !error.is_retryable() {
165            return None;
166        }
167
168        let mut strategy = RetryStrategy::default();
169
170        match error {
171            LarkAPIError::ApiError { code, .. } => {
172                if let Some(error_code) = LarkErrorCode::from_code(*code) {
173                    strategy.max_attempts = match error_code {
174                        LarkErrorCode::TooManyRequests => 3,
175                        LarkErrorCode::InternalServerError => 5,
176                        LarkErrorCode::ServiceUnavailable => 3,
177                        LarkErrorCode::GatewayTimeout => 3,
178                        _ => 3,
179                    };
180                    strategy.base_delay =
181                        Duration::from_secs(error_code.suggested_retry_delay().unwrap_or(5));
182                }
183            }
184            LarkAPIError::RequestError(req_err) => {
185                if req_err.contains("timeout") || req_err.contains("timed out") {
186                    strategy.max_attempts = 3;
187                    strategy.base_delay = Duration::from_secs(5);
188                } else if req_err.contains("connect") || req_err.contains("connection") {
189                    strategy.max_attempts = 5;
190                    strategy.base_delay = Duration::from_secs(10);
191                }
192            }
193            _ => {
194                strategy.max_attempts = 3;
195                strategy.base_delay = Duration::from_secs(5);
196            }
197        }
198
199        Some(strategy)
200    }
201
202    /// 格式化错误信息供用户显示
203    pub fn format_user_error(error: &LarkAPIError) -> String {
204        match error {
205            LarkAPIError::ApiError { code, .. } => {
206                if let Some(error_code) = LarkErrorCode::from_code(*code) {
207                    error_code.detailed_description().to_string()
208                } else {
209                    format!("API调用失败,错误码: {code}")
210                }
211            }
212            _ => error.user_friendly_message(),
213        }
214    }
215
216    /// 创建错误上下文信息
217    pub fn create_error_context(error: &LarkAPIError) -> ErrorContext {
218        let advice = Self::handle_error(error);
219        ErrorContext {
220            error_message: error.to_string(),
221            user_friendly_message: Self::format_user_error(error),
222            category: advice.category,
223            is_recoverable: advice.is_recoverable,
224            is_retryable: advice.is_retryable,
225            suggested_actions: advice.actions,
226            help_url: advice.help_url,
227            retry_strategy: Self::create_retry_strategy(error),
228        }
229    }
230}
231
232/// 错误处理建议
233#[derive(Debug, Clone)]
234pub struct ErrorHandlingAdvice {
235    /// 错误消息
236    pub message: String,
237    /// 错误类别
238    pub category: ErrorHandlingCategory,
239    /// 错误码(如果是API错误)
240    pub error_code: Option<LarkErrorCode>,
241    /// 是否可恢复
242    pub is_recoverable: bool,
243    /// 是否可重试
244    pub is_retryable: bool,
245    /// 建议的重试延迟(秒)
246    pub retry_delay: Option<u64>,
247    /// 建议的操作
248    pub actions: Vec<String>,
249    /// 帮助文档链接
250    pub help_url: Option<String>,
251}
252
253impl Default for ErrorHandlingAdvice {
254    fn default() -> Self {
255        Self {
256            message: String::new(),
257            category: ErrorHandlingCategory::Unknown,
258            error_code: None,
259            is_recoverable: false,
260            is_retryable: false,
261            retry_delay: None,
262            actions: Vec::new(),
263            help_url: None,
264        }
265    }
266}
267
268/// 错误处理类别
269#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
270pub enum ErrorHandlingCategory {
271    /// 认证错误
272    Authentication,
273    /// 权限错误
274    Permission,
275    /// 客户端错误
276    ClientError,
277    /// 服务器错误
278    ServerError,
279    /// 网络错误
280    NetworkError,
281    /// 限流错误
282    RateLimit,
283    /// 系统错误
284    SystemError,
285    /// 未知错误
286    Unknown,
287}
288
289/// 重试策略
290#[derive(Debug, Clone)]
291pub struct RetryStrategy {
292    /// 最大重试次数
293    pub max_attempts: u32,
294    /// 基础延迟时间
295    pub base_delay: Duration,
296    /// 是否使用指数退避
297    pub use_exponential_backoff: bool,
298    /// 最大延迟时间
299    pub max_delay: Duration,
300}
301
302impl Default for RetryStrategy {
303    fn default() -> Self {
304        Self {
305            max_attempts: 3,
306            base_delay: Duration::from_secs(5),
307            use_exponential_backoff: true,
308            max_delay: Duration::from_secs(60),
309        }
310    }
311}
312
313impl RetryStrategy {
314    /// 计算指定尝试次数的延迟时间
315    pub fn calculate_delay(&self, attempt: u32) -> Duration {
316        if !self.use_exponential_backoff {
317            return self.base_delay;
318        }
319
320        let multiplier = 2_u32.pow(attempt);
321        let delay = self.base_delay * multiplier;
322        std::cmp::min(delay, self.max_delay)
323    }
324}
325
326/// 错误上下文信息
327#[derive(Debug, Clone)]
328pub struct ErrorContext {
329    /// 原始错误消息
330    pub error_message: String,
331    /// 用户友好的错误消息
332    pub user_friendly_message: String,
333    /// 错误类别
334    pub category: ErrorHandlingCategory,
335    /// 是否可恢复
336    pub is_recoverable: bool,
337    /// 是否可重试
338    pub is_retryable: bool,
339    /// 建议的操作
340    pub suggested_actions: Vec<String>,
341    /// 帮助文档链接
342    pub help_url: Option<String>,
343    /// 重试策略
344    pub retry_strategy: Option<RetryStrategy>,
345}
346
347impl ErrorContext {
348    /// 打印详细的错误信息
349    pub fn print_details(&self) {
350        println!("❌ 错误: {}", self.user_friendly_message);
351        println!("类别: {:?}", self.category);
352
353        if self.is_recoverable {
354            println!("✅ 此错误可以恢复");
355        } else {
356            println!("⚠️ 此错误可能需要人工干预");
357        }
358
359        if self.is_retryable {
360            println!("🔄 此错误可以重试");
361            if let Some(strategy) = &self.retry_strategy {
362                println!("   建议最大重试次数: {}", strategy.max_attempts);
363                println!("   基础延迟时间: {:?}", strategy.base_delay);
364            }
365        }
366
367        if !self.suggested_actions.is_empty() {
368            println!("\n💡 建议操作:");
369            for (i, action) in self.suggested_actions.iter().enumerate() {
370                println!("   {}. {}", i + 1, action);
371            }
372        }
373
374        if let Some(url) = &self.help_url {
375            println!("\n🔗 帮助文档: {url}");
376        }
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383
384    #[test]
385    fn test_error_helper_api_error() {
386        let error = LarkAPIError::api_error(403, "Forbidden", None);
387        let advice = ErrorHelper::handle_error(&error);
388
389        assert_eq!(advice.category, ErrorHandlingCategory::Permission);
390        assert!(advice.is_recoverable);
391        assert!(!advice.actions.is_empty());
392    }
393
394    #[test]
395    fn test_retry_strategy() {
396        let error = LarkAPIError::api_error(429, "Too Many Requests", None);
397        let strategy = ErrorHelper::create_retry_strategy(&error);
398
399        assert!(strategy.is_some());
400        let strategy = strategy.unwrap();
401        assert_eq!(strategy.max_attempts, 3);
402    }
403
404    #[test]
405    fn test_error_context() {
406        let error = LarkAPIError::MissingAccessToken;
407        let context = ErrorHelper::create_error_context(&error);
408
409        assert_eq!(context.category, ErrorHandlingCategory::Authentication);
410        assert!(context.is_recoverable);
411    }
412}