Skip to main content

openlark_client/
error.rs

1//! OpenLark Client 错误类型定义
2//!
3//! 基于 openlark-core 的现代化错误处理系统
4//! 直接使用 CoreError,提供类型安全和用户友好的错误管理
5
6use openlark_core::error::{CoreError, ErrorTrait, ErrorType};
7
8/// 🚨 OpenLark 客户端错误类型
9///
10/// 直接类型别名,充分利用 CoreError 的强大功能
11pub type Error = CoreError;
12
13/// 📦 客户端结果类型别名
14pub type Result<T> = std::result::Result<T, Error>;
15
16// ============================================================================
17// 便利错误创建函数(重新导出核心函数)
18// ============================================================================
19
20/// 创建网络错误
21pub fn network_error(message: impl Into<String>) -> Error {
22    openlark_core::error::network_error(message)
23}
24
25/// 创建认证错误
26pub fn authentication_error(message: impl Into<String>) -> Error {
27    openlark_core::error::authentication_error(message)
28}
29
30/// 创建访问令牌格式/内容无效错误
31pub fn token_invalid_error(detail: impl Into<String>) -> Error {
32    openlark_core::error::token_invalid_error(detail)
33}
34
35/// 创建访问令牌过期错误(飞书通用码 99991677)
36pub fn token_expired_error(detail: impl Into<String>) -> Error {
37    openlark_core::error::token_expired_error(detail)
38}
39
40/// 创建缺少权限 scope 的错误
41pub fn permission_missing_error(scopes: &[impl AsRef<str>]) -> Error {
42    openlark_core::error::permission_missing_error(scopes)
43}
44
45/// 创建 SSO 令牌无效错误
46pub fn sso_token_invalid_error(detail: impl Into<String>) -> Error {
47    openlark_core::error::sso_token_invalid_error(detail)
48}
49
50/// 创建身份标识非法错误
51pub fn user_identity_invalid_error(desc: impl Into<String>) -> Error {
52    openlark_core::error::user_identity_invalid_error(desc)
53}
54
55/// 创建API错误
56pub fn api_error(
57    raw_code: i32,
58    endpoint: impl Into<String>,
59    message: impl Into<String>,
60    request_id: Option<String>,
61) -> Error {
62    openlark_core::error::api_error(raw_code, endpoint, message, request_id)
63}
64
65/// 创建验证错误
66pub fn validation_error(field: impl Into<String>, message: impl Into<String>) -> Error {
67    openlark_core::error::validation_error(field, message)
68}
69
70/// 创建配置错误
71pub fn configuration_error(message: impl Into<String>) -> Error {
72    openlark_core::error::configuration_error(message)
73}
74
75/// 创建序列化错误
76pub fn serialization_error(message: impl Into<String>) -> Error {
77    openlark_core::error::serialization_error(message, None::<serde_json::Error>)
78}
79
80/// 创建业务逻辑错误
81pub fn business_error(_code: impl Into<String>, message: impl Into<String>) -> Error {
82    openlark_core::error::business_error(message)
83}
84
85/// 创建超时错误
86pub fn timeout_error(operation: impl Into<String>) -> Error {
87    use std::time::Duration;
88    openlark_core::error::timeout_error(Duration::from_secs(30), Some(operation.into()))
89}
90
91/// 创建限流错误
92pub fn rate_limit_error(retry_after: Option<u64>) -> Error {
93    use std::time::Duration;
94    openlark_core::error::rate_limit_error(
95        100,
96        Duration::from_secs(60),
97        retry_after.map(Duration::from_secs),
98    )
99}
100
101/// 创建服务不可用错误
102pub fn service_unavailable_error(service: impl Into<String>) -> Error {
103    use std::time::Duration;
104    openlark_core::error::service_unavailable_error(service, Some(Duration::from_secs(60)))
105}
106
107/// 创建内部错误
108pub fn internal_error(message: impl Into<String>) -> Error {
109    openlark_core::error::api_error(500, "internal", message, None::<String>)
110}
111
112// ============================================================================
113// 错误扩展功能
114// ============================================================================
115
116/// 客户端错误扩展特征,提供错误恢复建议和步骤
117pub trait ClientErrorExt {
118    /// 获取错误建议
119    fn suggestion(&self) -> &'static str;
120
121    /// 获取错误恢复步骤
122    fn recovery_steps(&self) -> Vec<&'static str>;
123}
124
125impl ClientErrorExt for Error {
126    fn suggestion(&self) -> &'static str {
127        match self.error_type() {
128            ErrorType::Network => "检查网络连接,确认防火墙设置",
129            ErrorType::Authentication => "验证应用凭据,检查令牌有效性",
130            ErrorType::Api => "检查API参数,确认请求格式正确",
131            ErrorType::Validation => "验证输入参数格式和范围",
132            ErrorType::Configuration => "检查应用配置文件和环境变量",
133            ErrorType::Serialization => "确认数据格式正确,检查JSON结构",
134            ErrorType::Business => "确认业务逻辑条件,检查相关权限",
135            ErrorType::Timeout => "增加超时时间或优化请求内容",
136            ErrorType::RateLimit => "稍后重试,考虑降低请求频率",
137            ErrorType::ServiceUnavailable => "稍后重试,检查服务状态",
138            ErrorType::Internal => "联系技术支持,提供错误详情",
139            ErrorType::ResponseTooLarge => "减小请求数据量或增大 max_response_size 配置",
140        }
141    }
142
143    fn recovery_steps(&self) -> Vec<&'static str> {
144        match self.error_type() {
145            ErrorType::Network => vec![
146                "检查网络连接状态",
147                "确认代理设置正确",
148                "验证防火墙规则",
149                "尝试切换网络环境",
150            ],
151            ErrorType::Authentication => vec![
152                "验证应用ID和密钥正确性",
153                "检查令牌是否过期",
154                "确认应用权限配置",
155                "重新生成访问令牌",
156            ],
157            ErrorType::Api => vec![
158                "检查请求参数格式",
159                "确认API端点正确",
160                "验证请求体结构",
161                "查阅API文档",
162            ],
163            ErrorType::Validation => vec![
164                "检查必填字段",
165                "验证数据格式和范围",
166                "确认字段类型正确",
167                "参考输入示例",
168            ],
169            ErrorType::Configuration => vec![
170                "检查环境变量设置",
171                "验证配置文件格式",
172                "确认应用权限配置",
173                "重新加载配置",
174            ],
175            ErrorType::Serialization => vec![
176                "检查JSON格式正确性",
177                "验证数据结构匹配",
178                "确认字段类型一致",
179                "使用在线JSON验证工具",
180            ],
181            ErrorType::Business => vec![
182                "检查业务规则约束",
183                "确认用户权限充分",
184                "验证数据完整性",
185                "联系业务负责人",
186            ],
187            ErrorType::Timeout => vec![
188                "增加请求超时时间",
189                "优化网络环境",
190                "减少请求数据量",
191                "考虑分批处理",
192            ],
193            ErrorType::RateLimit => vec![
194                "等待后重试",
195                "降低请求频率",
196                "实施退避策略",
197                "联系技术支持提高限额",
198            ],
199            ErrorType::ServiceUnavailable => vec![
200                "稍后重试请求",
201                "检查服务状态页面",
202                "切换到备用方案",
203                "联系技术支持",
204            ],
205            ErrorType::Internal => vec![
206                "记录详细错误信息",
207                "检查系统日志",
208                "重启相关服务",
209                "联系技术支持",
210            ],
211            ErrorType::ResponseTooLarge => vec![
212                "减小请求数据量",
213                "增大 max_response_size 配置",
214                "分批请求数据",
215                "联系技术支持确认数据规模",
216            ],
217        }
218    }
219}
220
221// ============================================================================
222// 类型转换
223// ============================================================================
224
225// 注意: reqwest::Error -> CoreError 转换已在 openlark-core 中实现
226// 这里不需要重复实现,直接使用 CoreError 的转换机制
227
228// 注意: 不能为外部类型实现 From,因为这些类型由 CoreError 定义在 openlark-core 中
229// 请使用对应的函数来进行错误转换
230
231// ============================================================================
232// 便利函数
233// ============================================================================
234
235/// 🔧 从 openlark-core SDKResult 转换为客户端 Result 的便利函数
236///
237/// 这个函数现在只是类型转换,因为我们直接使用 CoreError
238///
239/// # 示例
240///
241/// ```rust
242/// use openlark_client::error::from_sdk_result;
243///
244/// let core_result: openlark_core::SDKResult<String> = Ok("success".to_string());
245/// let client_result = from_sdk_result(core_result);
246/// assert!(client_result.is_ok());
247/// ```
248pub fn from_sdk_result<T>(result: openlark_core::SDKResult<T>) -> Result<T> {
249    result
250}
251
252/// 🔧 创建带有上下文的错误的便利函数
253pub fn with_context<T>(
254    result: Result<T>,
255    context_key: impl Into<String>,
256    context_value: impl Into<String>,
257) -> Result<T> {
258    let key = context_key.into();
259    let value = context_value.into();
260    result.map_err(|err| err.with_context_kv(key, value))
261}
262
263/// 🔧 创建带有操作上下文的错误的便利函数
264pub fn with_operation_context<T>(
265    result: Result<T>,
266    operation: impl Into<String>,
267    component: impl Into<String>,
268) -> Result<T> {
269    let operation = operation.into();
270    let component = component.into();
271    result.map_err(|err| err.with_operation(operation, component))
272}
273
274// ============================================================================
275// 错误分析和报告
276// ============================================================================
277
278/// 错误分析器,提供详细的错误信息和恢复建议
279#[derive(Debug)]
280pub struct ErrorAnalyzer<'a> {
281    error: &'a Error,
282}
283
284impl<'a> ErrorAnalyzer<'a> {
285    /// 创建错误分析器
286    pub fn new(error: &'a Error) -> Self {
287        Self { error }
288    }
289
290    /// 获取详细的错误报告
291    pub fn detailed_report(&self) -> String {
292        let mut report = String::new();
293
294        report.push_str("🚨 错误分析报告\n");
295        report.push_str("================\n\n");
296
297        // 基本信息
298        report.push_str("📋 基本信息:\n");
299        report.push_str(&format!("  错误类型: {:?}\n", self.error.error_type()));
300        report.push_str(&format!("  错误代码: {:?}\n", self.error.error_code()));
301        report.push_str(&format!("  严重程度: {:?}\n", self.error.severity()));
302        report.push_str(&format!("  可重试: {}\n", self.error.is_retryable()));
303
304        if let Some(request_id) = self.error.context().request_id() {
305            report.push_str(&format!("  请求ID: {request_id}\n"));
306        }
307
308        report.push('\n');
309
310        // 错误消息
311        report.push_str("💬 错误消息:\n");
312        report.push_str(&format!("  技术消息: {}\n", self.error));
313        report.push_str(&format!(
314            "  用户消息: {}\n",
315            self.error.user_message().unwrap_or("未知错误")
316        ));
317
318        report.push('\n');
319
320        // 建议和恢复步骤
321        report.push_str("💡 建议:\n");
322        report.push_str(&format!("  {}\n", self.error.suggestion()));
323
324        report.push_str("\n🔧 恢复步骤:\n");
325        for (i, step) in self.error.recovery_steps().iter().enumerate() {
326            report.push_str(&format!("  {}. {}\n", i + 1, step));
327        }
328
329        report.push('\n');
330
331        // 上下文信息
332        if self.error.context().context_len() > 0 {
333            report.push_str("📊 上下文信息:\n");
334            for (key, value) in self.error.context().all_context() {
335                report.push_str(&format!("  {key}: {value}\n"));
336            }
337            report.push('\n');
338        }
339
340        // 时间戳
341        if let Some(timestamp) = self.error.context().timestamp() {
342            report.push_str(&format!("⏰ 发生时间: {timestamp:?}\n"));
343        }
344        report
345    }
346
347    /// 获取适合日志记录的错误摘要
348    pub fn log_summary(&self) -> String {
349        format!(
350            "Error[{:?}:{:?}] {} - {}",
351            self.error.error_type(),
352            self.error.error_code(),
353            self.error.user_message().unwrap_or("未知错误"),
354            if self.error.is_retryable() {
355                "(可重试)"
356            } else {
357                "(不可重试)"
358            }
359        )
360    }
361
362    /// 获取用户友好的错误消息,包含恢复建议
363    pub fn user_friendly_with_suggestion(&self) -> String {
364        format!(
365            "{}\n\n💡 建议: {}\n\n🔧 可以尝试:\n{}",
366            self.error.user_message().unwrap_or("未知错误"),
367            self.error.suggestion(),
368            self.error
369                .recovery_steps()
370                .iter()
371                .enumerate()
372                .map(|(i, step)| format!("{}. {}", i + 1, step))
373                .collect::<Vec<_>>()
374                .join("\n")
375        )
376    }
377}
378
379// 注意: 不能为外部类型 CoreError 定义 inherent impl
380// 请使用 ClientErrorExt trait 来获得扩展功能
381
382// ============================================================================
383// 测试
384// ============================================================================
385
386#[cfg(test)]
387#[allow(unused_imports)]
388mod tests {
389    use super::*;
390
391    #[test]
392    fn test_error_convenience_functions() {
393        let network_err = network_error("连接失败");
394        assert!(network_err.is_network_error());
395        assert!(network_err.is_retryable());
396
397        let auth_err = authentication_error("令牌无效");
398        assert!(auth_err.is_auth_error());
399        assert!(!auth_err.is_retryable());
400
401        let validation_err = validation_error("email", "邮箱格式不正确");
402        assert!(validation_err.is_validation_error());
403        assert!(!validation_err.is_retryable());
404    }
405
406    #[test]
407    fn test_error_analyzer() {
408        let error = api_error(404, "/users", "用户不存在", Some("req-123".to_string()));
409        let analyzer = ErrorAnalyzer::new(&error);
410
411        let report = analyzer.detailed_report();
412        assert!(report.contains("错误分析报告"));
413        assert!(report.contains("API错误"));
414        assert!(report.contains("req-123"));
415
416        let summary = analyzer.log_summary();
417        assert!(summary.contains("Error"));
418        assert!(summary.contains("Api"));
419
420        let user_msg = analyzer.user_friendly_with_suggestion();
421        assert!(user_msg.contains("建议"));
422        assert!(user_msg.contains("可以尝试"));
423    }
424
425    #[test]
426    fn test_client_error_ext() {
427        let error = timeout_error("数据同步");
428
429        assert!(!error.is_network_error());
430        assert!(!error.is_auth_error());
431        assert!(!error.is_business_error());
432        assert!(error.is_retryable());
433
434        let suggestion = error.suggestion();
435        assert!(!suggestion.is_empty());
436
437        let steps = error.recovery_steps();
438        assert!(!steps.is_empty());
439        assert!(steps.contains(&"增加请求超时时间"));
440    }
441
442    #[test]
443    fn test_error_conversions() {
444        // 测试 JSON 错误转换
445        let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
446        let error: Error = json_err.into();
447        assert!(error.is_serialization_error());
448
449        // 测试 tokio 超时错误转换
450        // let timeout_err = tokio::time::error::Elapsed {}; // Private field
451        // let error: Error = timeout_err.into();
452        // assert!(error.is_timeout_error());
453        // assert!(error.is_retryable());
454    }
455
456    #[test]
457    fn test_context_functions() {
458        let result: Result<i32> = Err(validation_error("age", "年龄不能为负数"));
459
460        let contextual_result = with_context(result, "user_id", "12345");
461        assert!(contextual_result.is_err());
462
463        let error = contextual_result.unwrap_err();
464        // 我们现在使用结构化上下文,验证上下文内容而不是字符串
465        // assert!(error.to_string().contains("user_id: 12345"));
466        assert_eq!(error.context().get_context("user_id"), Some("12345"));
467    }
468
469    #[test]
470    fn test_sdk_result_conversion() {
471        // 成功情况
472        let core_result: openlark_core::SDKResult<String> = Ok("success".to_string());
473        let client_result: Result<String> = from_sdk_result(core_result);
474        assert!(client_result.is_ok());
475        assert_eq!(client_result.unwrap(), "success");
476
477        // 失败情况
478        let core_result: openlark_core::SDKResult<String> = Err(network_error("网络错误"));
479        let client_result: Result<String> = from_sdk_result(core_result);
480        assert!(client_result.is_err());
481        assert!(client_result.unwrap_err().is_network_error());
482    }
483
484    #[test]
485    fn test_api_error_function() {
486        let error = api_error(
487            500,
488            "/api/test",
489            "服务器内部错误",
490            Some("req-456".to_string()),
491        );
492        assert!(error.is_api_error());
493        let analyzer = ErrorAnalyzer::new(&error);
494        let report = analyzer.detailed_report();
495        assert!(report.contains("服务器内部错误"));
496    }
497
498    #[test]
499    fn test_validation_error_function() {
500        let error = validation_error("field_name", "字段值为空");
501        assert!(error.is_validation_error());
502        let analyzer = ErrorAnalyzer::new(&error);
503        let user_msg = analyzer.user_friendly_with_suggestion();
504        assert!(user_msg.contains("建议"));
505    }
506
507    #[test]
508    fn test_configuration_error_function() {
509        let error = configuration_error("配置文件缺失");
510        assert!(error.is_config_error());
511    }
512
513    #[test]
514    fn test_serialization_error_function() {
515        let error = serialization_error("JSON解析失败");
516        assert!(error.is_serialization_error());
517    }
518
519    #[test]
520    fn test_business_error_function() {
521        let error = business_error("ERR_001", "业务规则验证失败");
522        assert!(error.is_business_error());
523    }
524
525    #[test]
526    fn test_timeout_error_function() {
527        let error = timeout_error("数据库查询超时");
528        assert!(error.is_timeout_error());
529        assert!(error.is_retryable());
530    }
531
532    #[test]
533    fn test_rate_limit_error_function() {
534        let error = rate_limit_error(Some(60));
535        assert!(error.is_rate_limited());
536    }
537
538    #[test]
539    fn test_service_unavailable_error_function() {
540        let error = service_unavailable_error("支付服务");
541        assert!(error.is_service_unavailable_error());
542    }
543
544    #[test]
545    fn test_internal_error_function() {
546        let error = internal_error("系统内部错误");
547        assert!(!error.is_user_error());
548    }
549
550    #[test]
551    fn test_token_invalid_error_function() {
552        let error = token_invalid_error("token格式不正确");
553        assert!(error.is_auth_error());
554    }
555
556    #[test]
557    fn test_token_expired_error_function() {
558        let error = token_expired_error("token已过期");
559        assert!(error.is_auth_error());
560    }
561
562    #[test]
563    fn test_permission_missing_error_function() {
564        let scopes = vec!["read:user", "write:docs"];
565        let error = permission_missing_error(&scopes);
566        assert!(error.is_auth_error());
567    }
568
569    #[test]
570    fn test_sso_token_invalid_error_function() {
571        let error = sso_token_invalid_error("SSO令牌无效");
572        assert!(error.is_auth_error());
573    }
574
575    #[test]
576    fn test_user_identity_invalid_error_function() {
577        let error = user_identity_invalid_error("用户身份标识非法");
578        assert!(error.is_auth_error());
579    }
580
581    #[test]
582    fn test_error_analyzer_log_summary() {
583        let error = network_error("连接超时");
584        let analyzer = ErrorAnalyzer::new(&error);
585        let summary = analyzer.log_summary();
586        assert!(summary.contains("Network"));
587        assert!(summary.contains("可重试") || summary.contains("不可重试"));
588    }
589
590    #[test]
591    fn test_error_analyzer_user_friendly() {
592        let error = api_error(404, "/users/123", "用户不存在", None);
593        let analyzer = ErrorAnalyzer::new(&error);
594        let friendly = analyzer.user_friendly_with_suggestion();
595        assert!(friendly.contains("建议"));
596        assert!(friendly.contains("可以尝试"));
597    }
598
599    #[test]
600    fn test_with_operation_context() {
601        let result: Result<i32> = Err(network_error("网络错误"));
602        let contextual_result = with_operation_context(result, "test_operation", "TestComponent");
603        assert!(contextual_result.is_err());
604        let error = contextual_result.unwrap_err();
605        assert_eq!(
606            error.context().get_context("operation"),
607            Some("test_operation")
608        );
609        assert_eq!(
610            error.context().get_context("component"),
611            Some("TestComponent")
612        );
613    }
614
615    #[test]
616    fn test_with_operation_context_updates_timeout_operation_field() {
617        use std::time::Duration;
618
619        let result: Result<i32> = Err(openlark_core::error::timeout_error(
620            Duration::from_secs(3),
621            Some("old_operation".to_string()),
622        ));
623
624        let contextual_result = with_operation_context(result, "new_operation", "ClientLayer");
625        assert!(contextual_result.is_err());
626
627        match contextual_result.unwrap_err() {
628            CoreError::Timeout {
629                operation, ref ctx, ..
630            } => {
631                assert_eq!(operation.as_deref(), Some("new_operation"));
632                assert_eq!(ctx.operation(), Some("new_operation"));
633                assert_eq!(ctx.component(), Some("ClientLayer"));
634            }
635            other => panic!("expected timeout error, got {:?}", other.error_type()),
636        }
637    }
638
639    #[test]
640    fn test_all_error_types_suggestion() {
641        let error_types = vec![
642            (network_error("test"), "检查网络连接"),
643            (authentication_error("test"), "验证应用凭据"),
644            (api_error(500, "/test", "test", None), "检查API参数"),
645            (validation_error("field", "test"), "验证输入参数"),
646            (configuration_error("test"), "检查应用配置"),
647            (serialization_error("test"), "确认数据格式"),
648            (business_error("code", "test"), "确认业务逻辑"),
649            (timeout_error("test"), "增加超时时间"),
650            (rate_limit_error(None), "稍后重试"),
651            (service_unavailable_error("svc"), "稍后重试"),
652            (internal_error("test"), "联系技术支持"),
653        ];
654
655        for (error, expected_keyword) in error_types {
656            let suggestion = error.suggestion();
657            assert!(
658                suggestion.contains(expected_keyword) || !suggestion.is_empty(),
659                "Error type {:?} should have meaningful suggestion",
660                error.error_type()
661            );
662        }
663    }
664
665    #[test]
666    fn test_all_error_types_recovery_steps() {
667        let error_types = vec![
668            network_error("test"),
669            authentication_error("test"),
670            api_error(500, "/test", "test", None),
671            validation_error("field", "test"),
672            configuration_error("test"),
673            serialization_error("test"),
674            business_error("code", "test"),
675            timeout_error("test"),
676            rate_limit_error(None),
677            service_unavailable_error("svc"),
678            internal_error("test"),
679        ];
680
681        for error in error_types {
682            let steps = error.recovery_steps();
683            assert!(
684                !steps.is_empty(),
685                "Error type {:?} should have recovery steps",
686                error.error_type()
687            );
688            assert!(
689                steps.len() >= 3,
690                "Error type {:?} should have at least 3 recovery steps",
691                error.error_type()
692            );
693        }
694    }
695}