open_lark/core/
error.rs

1use thiserror::Error;
2
3/// 飞书开放平台API错误类型
4///
5/// 包含所有可能的API调用错误,提供详细的错误信息和处理建议。
6/// 支持错误分类、重试判断和用户友好的错误消息。
7///
8/// # 错误类型分类
9///
10/// - **网络错误**: RequestError, IOErr, UrlParseError
11/// - **数据错误**: DeserializeError, DataError
12/// - **参数错误**: IllegalParamError, BadRequest
13/// - **API错误**: ApiError, APIError
14/// - **认证错误**: MissingAccessToken
15///
16/// # 错误处理示例
17///
18/// ```rust
19/// use open_lark::core::error::LarkAPIError;
20///
21/// fn handle_api_error(error: LarkAPIError) {
22///     match error {
23///         LarkAPIError::MissingAccessToken => {
24///             println!("请检查应用凭据配置");
25///         }
26///         LarkAPIError::ApiError { code, message, .. } if code == 403 => {
27///             println!("权限不足: {}", message);
28///         }
29///         err if err.is_retryable() => {
30///             println!("网络错误,可以重试: {}", err.user_friendly_message());
31///         }
32///         _ => {
33///             println!("操作失败: {}", error.user_friendly_message());
34///         }
35///     }
36/// }
37/// ```
38///
39/// # 最佳实践
40///
41/// - 使用 `is_retryable()` 判断是否可以重试
42/// - 使用 `user_friendly_message()` 获取用户友好的错误提示
43/// - 使用 `is_permission_error()` 检查权限相关错误
44#[derive(Error, Debug)]
45pub enum LarkAPIError {
46    /// 输入输出错误
47    ///
48    /// 通常由文件操作、网络IO等底层操作失败引起。
49    #[error("IO error: {0}")]
50    IOErr(String),
51
52    /// 非法参数错误
53    ///
54    /// 当传入的参数不符合API要求时抛出,如无效的ID格式、超出范围的值等。
55    #[error("Invalid parameter: {0}")]
56    IllegalParamError(String),
57
58    /// JSON反序列化错误
59    ///
60    /// 当API响应的JSON格式无法解析为预期的数据结构时发生。
61    #[error("JSON deserialization error: {0}")]
62    DeserializeError(String),
63
64    /// HTTP请求失败
65    ///
66    /// 网络请求层面的错误,如连接超时、DNS解析失败等。通常可以重试。
67    #[error("HTTP request failed: {0}")]
68    RequestError(String),
69
70    /// URL解析错误
71    ///
72    /// 当构建的API请求URL格式不正确时发生。
73    #[error("URL parse error: {0}")]
74    UrlParseError(String),
75
76    /// 增强的API错误
77    ///
78    /// 包含错误码、消息和请求ID的完整错误信息,便于调试和问题追踪。
79    #[error("API error: {message} (code: {code}, request_id: {request_id:?})")]
80    ApiError {
81        /// API错误码
82        code: i32,
83        /// 错误消息
84        message: String,
85        /// 请求ID,用于问题追踪
86        request_id: Option<String>,
87    },
88
89    /// 缺少访问令牌
90    ///
91    /// 当API调用需要认证但未提供有效的访问令牌时发生。
92    #[error("Missing access token")]
93    MissingAccessToken,
94
95    /// 错误的请求
96    ///
97    /// 请求格式或内容不符合API规范。
98    #[error("Bad request: {0}")]
99    BadRequest(String),
100
101    /// 数据处理错误
102    ///
103    /// 数据验证、转换或处理过程中发生的错误。
104    #[error("Data error: {0}")]
105    DataError(String),
106
107    /// 标准API响应错误
108    ///
109    /// 飞书开放平台返回的标准错误响应,包含完整的错误信息。
110    #[error("API error: {msg} (code: {code})")]
111    APIError {
112        /// API错误码
113        code: i32,
114        /// 错误消息
115        msg: String,
116        /// 详细错误信息
117        error: Option<String>,
118    },
119}
120
121impl Clone for LarkAPIError {
122    fn clone(&self) -> Self {
123        match self {
124            Self::IOErr(msg) => Self::IOErr(msg.clone()),
125            Self::IllegalParamError(msg) => Self::IllegalParamError(msg.clone()),
126            Self::DeserializeError(msg) => Self::DeserializeError(msg.clone()),
127            Self::RequestError(msg) => Self::RequestError(msg.clone()),
128            Self::UrlParseError(msg) => Self::UrlParseError(msg.clone()),
129            Self::ApiError {
130                code,
131                message,
132                request_id,
133            } => Self::ApiError {
134                code: *code,
135                message: message.clone(),
136                request_id: request_id.clone(),
137            },
138            Self::MissingAccessToken => Self::MissingAccessToken,
139            Self::BadRequest(msg) => Self::BadRequest(msg.clone()),
140            Self::DataError(msg) => Self::DataError(msg.clone()),
141            Self::APIError { code, msg, error } => Self::APIError {
142                code: *code,
143                msg: msg.clone(),
144                error: error.clone(),
145            },
146        }
147    }
148}
149
150impl From<std::io::Error> for LarkAPIError {
151    fn from(err: std::io::Error) -> Self {
152        Self::IOErr(err.to_string())
153    }
154}
155
156impl From<serde_json::Error> for LarkAPIError {
157    fn from(err: serde_json::Error) -> Self {
158        Self::DeserializeError(err.to_string())
159    }
160}
161
162impl From<reqwest::Error> for LarkAPIError {
163    fn from(err: reqwest::Error) -> Self {
164        Self::RequestError(err.to_string())
165    }
166}
167
168impl From<url::ParseError> for LarkAPIError {
169    fn from(err: url::ParseError) -> Self {
170        Self::UrlParseError(err.to_string())
171    }
172}
173
174/// 错误严重程度
175///
176/// 用于对错误进行分级,帮助确定错误处理策略和用户提示方式。
177///
178/// # 使用场景
179///
180/// - **Info**: 信息性消息,通常不需要特殊处理
181/// - **Warning**: 警告信息,可能影响功能但不阻断操作
182/// - **Error**: 错误信息,导致操作失败但系统可恢复
183/// - **Critical**: 严重错误,可能导致系统不稳定
184///
185/// # 示例
186///
187/// ```rust
188/// use open_lark::core::error::ErrorSeverity;
189///
190/// fn log_error(severity: ErrorSeverity, message: &str) {
191///     match severity {
192///         ErrorSeverity::Info => println!("ℹ️  {}", message),
193///         ErrorSeverity::Warning => println!("⚠️  {}", message),
194///         ErrorSeverity::Error => eprintln!("❌ {}", message),
195///         ErrorSeverity::Critical => eprintln!("🚨 CRITICAL: {}", message),
196///     }
197/// }
198/// ```
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
200pub enum ErrorSeverity {
201    /// 信息级别 - 一般性提示信息
202    Info,
203    /// 警告级别 - 可能的问题但不影响核心功能
204    Warning,
205    /// 错误级别 - 操作失败但系统可恢复
206    Error,
207    /// 严重错误级别 - 可能影响系统稳定性
208    Critical,
209}
210
211impl LarkAPIError {
212    /// 创建包含上下文信息的API错误
213    ///
214    /// # 参数
215    /// - `code`: 错误码
216    /// - `message`: 错误消息
217    /// - `request_id`: 请求ID,用于问题追踪
218    ///
219    /// # 示例
220    /// ```rust
221    /// use open_lark::core::error::LarkAPIError;
222    ///
223    /// let error = LarkAPIError::api_error(
224    ///     403,
225    ///     "权限不足",
226    ///     Some("req_123456".to_string())
227    /// );
228    /// ```
229    pub fn api_error<M: Into<String>>(code: i32, message: M, request_id: Option<String>) -> Self {
230        Self::ApiError {
231            code,
232            message: message.into(),
233            request_id,
234        }
235    }
236
237    /// 创建非法参数错误
238    ///
239    /// # 参数
240    /// - `message`: 错误详细信息
241    ///
242    /// # 示例
243    /// ```rust
244    /// use open_lark::core::error::LarkAPIError;
245    ///
246    /// let error = LarkAPIError::illegal_param("用户ID格式不正确");
247    /// ```
248    pub fn illegal_param<T: Into<String>>(message: T) -> Self {
249        Self::IllegalParamError(message.into())
250    }
251
252    /// 检查是否为权限相关错误
253    ///
254    /// 用于判断错误是否由权限不足引起,便于进行相应的错误处理。
255    ///
256    /// # 返回值
257    /// - `true`: 权限相关错误
258    /// - `false`: 其他类型错误
259    pub fn is_permission_error(&self) -> bool {
260        match self {
261            Self::ApiError { code, .. } => {
262                *code == 403
263                    || matches!(
264                        crate::core::error_codes::LarkErrorCode::from_code(*code),
265                        Some(crate::core::error_codes::LarkErrorCode::Forbidden)
266                    )
267            }
268            _ => false,
269        }
270    }
271
272    /// 检查错误是否可以重试
273    ///
274    /// 判断当前错误是否为临时性错误,可以通过重试解决。
275    /// 通常网络超时、连接失败等错误可以重试。
276    ///
277    /// # 返回值
278    /// - `true`: 可以重试的错误
279    /// - `false`: 不可重试的错误(如参数错误、权限错误)
280    ///
281    /// # 示例
282    /// ```rust
283    /// use open_lark::core::error::LarkAPIError;
284    ///
285    /// let error = LarkAPIError::RequestError("连接超时".to_string());
286    /// if error.is_retryable() {
287    ///     println!("可以重试该请求");
288    /// }
289    /// ```
290    pub fn is_retryable(&self) -> bool {
291        match self {
292            Self::ApiError { code, .. } => {
293                if let Some(error_code) = crate::core::error_codes::LarkErrorCode::from_code(*code)
294                {
295                    error_code.is_retryable()
296                } else {
297                    false
298                }
299            }
300            Self::RequestError(req_err) => {
301                req_err.contains("timeout")
302                    || req_err.contains("timed out")
303                    || req_err.contains("connect")
304                    || req_err.contains("connection")
305            }
306            _ => false,
307        }
308    }
309
310    /// 获取用户友好的错误消息
311    ///
312    /// 将技术性的错误信息转换为用户容易理解的提示信息。
313    /// 包含错误原因和可能的解决建议。
314    ///
315    /// # 返回值
316    /// 经过本地化和优化的错误消息字符串
317    ///
318    /// # 示例
319    /// ```rust
320    /// use open_lark::core::error::LarkAPIError;
321    ///
322    /// let error = LarkAPIError::MissingAccessToken;
323    /// println!("错误提示: {}", error.user_friendly_message());
324    /// // 输出: "缺少访问令牌,请检查认证配置"
325    /// ```
326    pub fn user_friendly_message(&self) -> String {
327        match self {
328            Self::ApiError { code, message, .. } => {
329                if let Some(error_code) = crate::core::error_codes::LarkErrorCode::from_code(*code)
330                {
331                    error_code.detailed_description().to_string()
332                } else {
333                    format!("API调用失败: {message} (错误码: {code})")
334                }
335            }
336            Self::MissingAccessToken => "缺少访问令牌,请检查认证配置".to_string(),
337            Self::IllegalParamError(msg) => format!("参数错误: {msg}"),
338            Self::RequestError(req_err) => {
339                if req_err.contains("timeout") || req_err.contains("timed out") {
340                    "请求超时,请检查网络连接".to_string()
341                } else if req_err.contains("connect") || req_err.contains("connection") {
342                    "连接失败,请检查网络设置".to_string()
343                } else {
344                    format!("网络请求失败: {req_err}")
345                }
346            }
347            _ => self.to_string(),
348        }
349    }
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355    use std::io::{Error as IOError, ErrorKind};
356
357    #[test]
358    fn test_lark_api_error_creation() {
359        let error = LarkAPIError::IOErr("test error".to_string());
360        assert!(matches!(error, LarkAPIError::IOErr(_)));
361    }
362
363    #[test]
364    fn test_error_display() {
365        let io_error = LarkAPIError::IOErr("file not found".to_string());
366        assert_eq!(io_error.to_string(), "IO error: file not found");
367
368        let param_error = LarkAPIError::IllegalParamError("invalid user_id".to_string());
369        assert_eq!(
370            param_error.to_string(),
371            "Invalid parameter: invalid user_id"
372        );
373
374        let deserialize_error = LarkAPIError::DeserializeError("invalid json".to_string());
375        assert_eq!(
376            deserialize_error.to_string(),
377            "JSON deserialization error: invalid json"
378        );
379
380        let request_error = LarkAPIError::RequestError("timeout".to_string());
381        assert_eq!(request_error.to_string(), "HTTP request failed: timeout");
382
383        let url_error = LarkAPIError::UrlParseError("malformed url".to_string());
384        assert_eq!(url_error.to_string(), "URL parse error: malformed url");
385
386        let missing_token = LarkAPIError::MissingAccessToken;
387        assert_eq!(missing_token.to_string(), "Missing access token");
388
389        let bad_request = LarkAPIError::BadRequest("malformed data".to_string());
390        assert_eq!(bad_request.to_string(), "Bad request: malformed data");
391
392        let data_error = LarkAPIError::DataError("validation failed".to_string());
393        assert_eq!(data_error.to_string(), "Data error: validation failed");
394    }
395
396    #[test]
397    fn test_api_error_with_context() {
398        let api_error = LarkAPIError::ApiError {
399            code: 403,
400            message: "Permission denied".to_string(),
401            request_id: Some("req_123".to_string()),
402        };
403
404        let display = api_error.to_string();
405        assert!(display.contains("403"));
406        assert!(display.contains("Permission denied"));
407        assert!(display.contains("req_123"));
408    }
409
410    #[test]
411    fn test_api_error_without_request_id() {
412        let api_error = LarkAPIError::ApiError {
413            code: 404,
414            message: "Not found".to_string(),
415            request_id: None,
416        };
417
418        let display = api_error.to_string();
419        assert!(display.contains("404"));
420        assert!(display.contains("Not found"));
421    }
422
423    #[test]
424    fn test_standard_api_error() {
425        let api_error = LarkAPIError::APIError {
426            code: 500,
427            msg: "Internal server error".to_string(),
428            error: Some("Database connection failed".to_string()),
429        };
430
431        let display = api_error.to_string();
432        assert!(display.contains("500"));
433        assert!(display.contains("Internal server error"));
434    }
435
436    #[test]
437    fn test_error_clone() {
438        let original = LarkAPIError::ApiError {
439            code: 403,
440            message: "Forbidden".to_string(),
441            request_id: Some("req_456".to_string()),
442        };
443
444        let cloned = original.clone();
445
446        match (&original, &cloned) {
447            (
448                LarkAPIError::ApiError {
449                    code: c1,
450                    message: m1,
451                    request_id: r1,
452                },
453                LarkAPIError::ApiError {
454                    code: c2,
455                    message: m2,
456                    request_id: r2,
457                },
458            ) => {
459                assert_eq!(c1, c2);
460                assert_eq!(m1, m2);
461                assert_eq!(r1, r2);
462            }
463            _ => panic!("Clone did not preserve variant"),
464        }
465    }
466
467    #[test]
468    fn test_from_std_io_error() {
469        let io_error = IOError::new(ErrorKind::NotFound, "file not found");
470        let lark_error: LarkAPIError = io_error.into();
471
472        match lark_error {
473            LarkAPIError::IOErr(msg) => assert!(msg.contains("file not found")),
474            _ => panic!("Wrong error type"),
475        }
476    }
477
478    #[test]
479    fn test_from_serde_json_error() {
480        let json_result: Result<serde_json::Value, _> = serde_json::from_str("invalid json");
481        let json_error = json_result.unwrap_err();
482        let lark_error: LarkAPIError = json_error.into();
483
484        match lark_error {
485            LarkAPIError::DeserializeError(msg) => assert!(!msg.is_empty()),
486            _ => panic!("Wrong error type"),
487        }
488    }
489
490    #[test]
491    fn test_from_reqwest_error() {
492        // Create a reqwest error (this is a bit tricky, so we'll test the conversion logic)
493        let error = LarkAPIError::RequestError("connection refused".to_string());
494        assert!(matches!(error, LarkAPIError::RequestError(_)));
495    }
496
497    #[test]
498    fn test_from_url_parse_error() {
499        let url_result = url::Url::parse("not a url");
500        let url_error = url_result.unwrap_err();
501        let lark_error: LarkAPIError = url_error.into();
502
503        match lark_error {
504            LarkAPIError::UrlParseError(msg) => assert!(!msg.is_empty()),
505            _ => panic!("Wrong error type"),
506        }
507    }
508
509    #[test]
510    fn test_error_severity_values() {
511        assert_eq!(ErrorSeverity::Info, ErrorSeverity::Info);
512        assert_ne!(ErrorSeverity::Info, ErrorSeverity::Warning);
513        assert_ne!(ErrorSeverity::Warning, ErrorSeverity::Error);
514        assert_ne!(ErrorSeverity::Error, ErrorSeverity::Critical);
515    }
516
517    #[test]
518    fn test_error_severity_debug() {
519        let info = ErrorSeverity::Info;
520        let debug_string = format!("{:?}", info);
521        assert_eq!(debug_string, "Info");
522    }
523
524    #[test]
525    fn test_error_severity_clone() {
526        let original = ErrorSeverity::Critical;
527        let cloned = original;
528        assert_eq!(original, cloned);
529    }
530
531    #[test]
532    fn test_api_error_constructor() {
533        let error = LarkAPIError::api_error(404, "Resource not found", Some("req_789".to_string()));
534
535        match error {
536            LarkAPIError::ApiError {
537                code,
538                message,
539                request_id,
540            } => {
541                assert_eq!(code, 404);
542                assert_eq!(message, "Resource not found");
543                assert_eq!(request_id, Some("req_789".to_string()));
544            }
545            _ => panic!("Wrong error type"),
546        }
547    }
548
549    #[test]
550    fn test_illegal_param_constructor() {
551        let error = LarkAPIError::illegal_param("Invalid user ID format");
552
553        match error {
554            LarkAPIError::IllegalParamError(msg) => {
555                assert_eq!(msg, "Invalid user ID format");
556            }
557            _ => panic!("Wrong error type"),
558        }
559    }
560
561    #[test]
562    fn test_is_permission_error() {
563        let permission_error = LarkAPIError::ApiError {
564            code: 403,
565            message: "Forbidden".to_string(),
566            request_id: None,
567        };
568        assert!(permission_error.is_permission_error());
569
570        let not_permission_error = LarkAPIError::ApiError {
571            code: 404,
572            message: "Not found".to_string(),
573            request_id: None,
574        };
575        assert!(!not_permission_error.is_permission_error());
576
577        let other_error = LarkAPIError::MissingAccessToken;
578        assert!(!other_error.is_permission_error());
579    }
580
581    #[test]
582    fn test_is_retryable() {
583        // Test retryable request errors
584        let timeout_error = LarkAPIError::RequestError("connection timeout".to_string());
585        assert!(timeout_error.is_retryable());
586
587        let connect_error = LarkAPIError::RequestError("connection refused".to_string());
588        assert!(connect_error.is_retryable());
589
590        let timed_out_error = LarkAPIError::RequestError("request timed out".to_string());
591        assert!(timed_out_error.is_retryable());
592
593        // Test non-retryable errors
594        let param_error = LarkAPIError::IllegalParamError("bad param".to_string());
595        assert!(!param_error.is_retryable());
596
597        let missing_token = LarkAPIError::MissingAccessToken;
598        assert!(!missing_token.is_retryable());
599
600        let other_request_error = LarkAPIError::RequestError("bad request".to_string());
601        assert!(!other_request_error.is_retryable());
602    }
603
604    #[test]
605    fn test_user_friendly_message() {
606        // Test missing access token
607        let missing_token = LarkAPIError::MissingAccessToken;
608        assert_eq!(
609            missing_token.user_friendly_message(),
610            "缺少访问令牌,请检查认证配置"
611        );
612
613        // Test parameter error
614        let param_error = LarkAPIError::IllegalParamError("invalid format".to_string());
615        assert_eq!(
616            param_error.user_friendly_message(),
617            "参数错误: invalid format"
618        );
619
620        // Test timeout
621        let timeout_error = LarkAPIError::RequestError("connection timeout".to_string());
622        assert_eq!(
623            timeout_error.user_friendly_message(),
624            "请求超时,请检查网络连接"
625        );
626
627        // Test connection error
628        let connect_error = LarkAPIError::RequestError("connection failed".to_string());
629        assert_eq!(
630            connect_error.user_friendly_message(),
631            "连接失败,请检查网络设置"
632        );
633
634        // Test generic request error
635        let generic_error = LarkAPIError::RequestError("unknown error".to_string());
636        assert_eq!(
637            generic_error.user_friendly_message(),
638            "网络请求失败: unknown error"
639        );
640
641        // Test other error types
642        let io_error = LarkAPIError::IOErr("file error".to_string());
643        assert!(io_error.user_friendly_message().contains("file error"));
644    }
645
646    #[test]
647    fn test_api_error_user_friendly_message() {
648        let api_error = LarkAPIError::ApiError {
649            code: 123456, // Unknown code
650            message: "Unknown error".to_string(),
651            request_id: Some("req_test".to_string()),
652        };
653
654        let friendly_msg = api_error.user_friendly_message();
655        assert!(friendly_msg.contains("Unknown error"));
656        assert!(friendly_msg.contains("123456"));
657    }
658
659    #[test]
660    fn test_all_error_variants_clone() {
661        let errors = vec![
662            LarkAPIError::IOErr("io".to_string()),
663            LarkAPIError::IllegalParamError("param".to_string()),
664            LarkAPIError::DeserializeError("json".to_string()),
665            LarkAPIError::RequestError("request".to_string()),
666            LarkAPIError::UrlParseError("url".to_string()),
667            LarkAPIError::ApiError {
668                code: 400,
669                message: "test".to_string(),
670                request_id: Some("req".to_string()),
671            },
672            LarkAPIError::MissingAccessToken,
673            LarkAPIError::BadRequest("bad".to_string()),
674            LarkAPIError::DataError("data".to_string()),
675            LarkAPIError::APIError {
676                code: 500,
677                msg: "error".to_string(),
678                error: Some("detail".to_string()),
679            },
680        ];
681
682        for error in errors {
683            let cloned = error.clone();
684            // Test that clone preserves the variant and data
685            assert_eq!(error.to_string(), cloned.to_string());
686        }
687    }
688
689    #[test]
690    fn test_debug_trait() {
691        let error = LarkAPIError::ApiError {
692            code: 403,
693            message: "Forbidden".to_string(),
694            request_id: Some("req_debug".to_string()),
695        };
696
697        let debug_string = format!("{:?}", error);
698        assert!(debug_string.contains("ApiError"));
699        assert!(debug_string.contains("403"));
700        assert!(debug_string.contains("Forbidden"));
701    }
702
703    #[test]
704    fn test_api_error_with_string_conversion() {
705        let error = LarkAPIError::api_error(500, String::from("Server error"), None);
706
707        match error {
708            LarkAPIError::ApiError { message, .. } => {
709                assert_eq!(message, "Server error");
710            }
711            _ => panic!("Wrong error type"),
712        }
713    }
714
715    #[test]
716    fn test_illegal_param_with_string_conversion() {
717        let error = LarkAPIError::illegal_param(String::from("Bad parameter"));
718
719        match error {
720            LarkAPIError::IllegalParamError(msg) => {
721                assert_eq!(msg, "Bad parameter");
722            }
723            _ => panic!("Wrong error type"),
724        }
725    }
726
727    #[test]
728    fn test_error_severity_hash() {
729        use std::collections::HashMap;
730
731        let mut map = HashMap::new();
732        map.insert(ErrorSeverity::Info, "info");
733        map.insert(ErrorSeverity::Warning, "warning");
734        map.insert(ErrorSeverity::Error, "error");
735        map.insert(ErrorSeverity::Critical, "critical");
736
737        assert_eq!(map.get(&ErrorSeverity::Info), Some(&"info"));
738        assert_eq!(map.get(&ErrorSeverity::Critical), Some(&"critical"));
739    }
740}