1use crate::registry::RegistryError;
7use openlark_core::error::{
8 ApiError, CoreError, ErrorCategory, ErrorCode, ErrorContext, ErrorTrait, ErrorType,
9};
10
11pub type Error = CoreError;
15
16pub type Result<T> = std::result::Result<T, Error>;
18
19pub fn network_error(message: impl Into<String>) -> Error {
25 openlark_core::error::network_error(message)
26}
27
28pub fn authentication_error(message: impl Into<String>) -> Error {
30 openlark_core::error::authentication_error(message)
31}
32
33pub fn token_invalid_error(detail: impl Into<String>) -> Error {
35 openlark_core::error::token_invalid_error(detail)
36}
37
38pub fn token_expired_error(detail: impl Into<String>) -> Error {
40 openlark_core::error::token_expired_error(detail)
41}
42
43pub fn permission_missing_error(scopes: &[impl AsRef<str>]) -> Error {
45 openlark_core::error::permission_missing_error(scopes)
46}
47
48pub fn sso_token_invalid_error(detail: impl Into<String>) -> Error {
50 openlark_core::error::sso_token_invalid_error(detail)
51}
52
53pub fn user_identity_invalid_error(desc: impl Into<String>) -> Error {
55 openlark_core::error::user_identity_invalid_error(desc)
56}
57
58pub fn from_feishu_response(
60 code: i32,
61 endpoint: impl Into<String>,
62 message: impl Into<String>,
63 request_id: Option<String>,
64) -> Error {
65 let mapped = ErrorCode::from_feishu_code(code).unwrap_or_else(|| ErrorCode::from_code(code));
66
67 let mut ctx = ErrorContext::new();
68 ctx.add_context("feishu_code", code.to_string());
69 if let Some(req) = request_id {
70 ctx.set_request_id(req);
71 }
72
73 let status = mapped
74 .http_status()
75 .unwrap_or_else(|| match mapped.category() {
76 ErrorCategory::RateLimit => 429,
77 ErrorCategory::Authentication
78 | ErrorCategory::Permission
79 | ErrorCategory::Parameter => 400,
80 ErrorCategory::Resource => 404,
81 _ => 500,
82 });
83
84 CoreError::Api(Box::new(ApiError {
85 status,
86 endpoint: endpoint.into().into(),
87 message: message.into(),
88 source: None,
89 code: mapped,
90 ctx: Box::new(ctx),
91 }))
92}
93
94pub fn api_error(
96 status: u16,
97 endpoint: impl Into<String>,
98 message: impl Into<String>,
99 request_id: Option<String>,
100) -> Error {
101 openlark_core::error::api_error(status, endpoint, message, request_id)
102}
103
104pub fn validation_error(field: impl Into<String>, message: impl Into<String>) -> Error {
106 openlark_core::error::validation_error(field, message)
107}
108
109pub fn configuration_error(message: impl Into<String>) -> Error {
111 openlark_core::error::configuration_error(message)
112}
113
114pub fn serialization_error(message: impl Into<String>) -> Error {
116 openlark_core::error::serialization_error(message, None::<serde_json::Error>)
117}
118
119pub fn business_error(_code: impl Into<String>, message: impl Into<String>) -> Error {
121 openlark_core::error::business_error(message)
122}
123
124pub fn timeout_error(operation: impl Into<String>) -> Error {
126 use std::time::Duration;
127 openlark_core::error::timeout_error(Duration::from_secs(30), Some(operation.into()))
128}
129
130pub fn rate_limit_error(retry_after: Option<u64>) -> Error {
132 use std::time::Duration;
133 openlark_core::error::rate_limit_error(
134 100,
135 Duration::from_secs(60),
136 retry_after.map(Duration::from_secs),
137 )
138}
139
140pub fn service_unavailable_error(service: impl Into<String>) -> Error {
142 use std::time::Duration;
143 openlark_core::error::service_unavailable_error(service, Some(Duration::from_secs(60)))
144}
145
146pub fn internal_error(message: impl Into<String>) -> Error {
148 openlark_core::error::api_error(500, "internal", message, None::<String>)
149}
150
151pub fn registry_error(err: RegistryError) -> Error {
153 internal_error(format!("服务注册表错误: {err}"))
154}
155
156pub trait ClientErrorExt {
162 fn suggestion(&self) -> &'static str;
164
165 fn recovery_steps(&self) -> Vec<&'static str>;
167}
168
169impl ClientErrorExt for Error {
170 fn suggestion(&self) -> &'static str {
171 match self.error_type() {
172 ErrorType::Network => "检查网络连接,确认防火墙设置",
173 ErrorType::Authentication => "验证应用凭据,检查令牌有效性",
174 ErrorType::Api => "检查API参数,确认请求格式正确",
175 ErrorType::Validation => "验证输入参数格式和范围",
176 ErrorType::Configuration => "检查应用配置文件和环境变量",
177 ErrorType::Serialization => "确认数据格式正确,检查JSON结构",
178 ErrorType::Business => "确认业务逻辑条件,检查相关权限",
179 ErrorType::Timeout => "增加超时时间或优化请求内容",
180 ErrorType::RateLimit => "稍后重试,考虑降低请求频率",
181 ErrorType::ServiceUnavailable => "稍后重试,检查服务状态",
182 ErrorType::Internal => "联系技术支持,提供错误详情",
183 ErrorType::ResponseTooLarge => "减小请求数据量或增大 max_response_size 配置",
184 }
185 }
186
187 fn recovery_steps(&self) -> Vec<&'static str> {
188 match self.error_type() {
189 ErrorType::Network => vec![
190 "检查网络连接状态",
191 "确认代理设置正确",
192 "验证防火墙规则",
193 "尝试切换网络环境",
194 ],
195 ErrorType::Authentication => vec![
196 "验证应用ID和密钥正确性",
197 "检查令牌是否过期",
198 "确认应用权限配置",
199 "重新生成访问令牌",
200 ],
201 ErrorType::Api => vec![
202 "检查请求参数格式",
203 "确认API端点正确",
204 "验证请求体结构",
205 "查阅API文档",
206 ],
207 ErrorType::Validation => vec![
208 "检查必填字段",
209 "验证数据格式和范围",
210 "确认字段类型正确",
211 "参考输入示例",
212 ],
213 ErrorType::Configuration => vec![
214 "检查环境变量设置",
215 "验证配置文件格式",
216 "确认应用权限配置",
217 "重新加载配置",
218 ],
219 ErrorType::Serialization => vec![
220 "检查JSON格式正确性",
221 "验证数据结构匹配",
222 "确认字段类型一致",
223 "使用在线JSON验证工具",
224 ],
225 ErrorType::Business => vec![
226 "检查业务规则约束",
227 "确认用户权限充分",
228 "验证数据完整性",
229 "联系业务负责人",
230 ],
231 ErrorType::Timeout => vec![
232 "增加请求超时时间",
233 "优化网络环境",
234 "减少请求数据量",
235 "考虑分批处理",
236 ],
237 ErrorType::RateLimit => vec![
238 "等待后重试",
239 "降低请求频率",
240 "实施退避策略",
241 "联系技术支持提高限额",
242 ],
243 ErrorType::ServiceUnavailable => vec![
244 "稍后重试请求",
245 "检查服务状态页面",
246 "切换到备用方案",
247 "联系技术支持",
248 ],
249 ErrorType::Internal => vec![
250 "记录详细错误信息",
251 "检查系统日志",
252 "重启相关服务",
253 "联系技术支持",
254 ],
255 ErrorType::ResponseTooLarge => vec![
256 "减小请求数据量",
257 "增大 max_response_size 配置",
258 "分批请求数据",
259 "联系技术支持确认数据规模",
260 ],
261 }
262 }
263}
264
265impl From<RegistryError> for Error {
277 fn from(err: RegistryError) -> Self {
278 registry_error(err)
279 }
280}
281
282pub fn from_sdk_result<T>(result: openlark_core::SDKResult<T>) -> Result<T> {
300 result
301}
302
303pub fn with_context<T>(
305 result: Result<T>,
306 context_key: impl Into<String>,
307 context_value: impl Into<String>,
308) -> Result<T> {
309 let key = context_key.into();
310 let value = context_value.into();
311 result.map_err(|err| err.with_context_kv(key, value))
312}
313
314pub fn with_operation_context<T>(
316 result: Result<T>,
317 operation: impl Into<String>,
318 component: impl Into<String>,
319) -> Result<T> {
320 let operation = operation.into();
321 let component = component.into();
322 result.map_err(|err| err.with_operation(operation, component))
323}
324
325#[derive(Debug)]
331pub struct ErrorAnalyzer<'a> {
332 error: &'a Error,
333}
334
335impl<'a> ErrorAnalyzer<'a> {
336 pub fn new(error: &'a Error) -> Self {
338 Self { error }
339 }
340
341 pub fn detailed_report(&self) -> String {
343 let mut report = String::new();
344
345 report.push_str("🚨 错误分析报告\n");
346 report.push_str("================\n\n");
347
348 report.push_str("📋 基本信息:\n");
350 report.push_str(&format!(" 错误类型: {:?}\n", self.error.error_type()));
351 report.push_str(&format!(" 错误代码: {:?}\n", self.error.error_code()));
352 report.push_str(&format!(" 严重程度: {:?}\n", self.error.severity()));
353 report.push_str(&format!(" 可重试: {}\n", self.error.is_retryable()));
354
355 if let Some(request_id) = self.error.context().request_id() {
356 report.push_str(&format!(" 请求ID: {request_id}\n"));
357 }
358
359 report.push('\n');
360
361 report.push_str("💬 错误消息:\n");
363 report.push_str(&format!(" 技术消息: {}\n", self.error));
364 report.push_str(&format!(
365 " 用户消息: {}\n",
366 self.error.user_message().unwrap_or("未知错误")
367 ));
368
369 report.push('\n');
370
371 report.push_str("💡 建议:\n");
373 report.push_str(&format!(" {}\n", self.error.suggestion()));
374
375 report.push_str("\n🔧 恢复步骤:\n");
376 for (i, step) in self.error.recovery_steps().iter().enumerate() {
377 report.push_str(&format!(" {}. {}\n", i + 1, step));
378 }
379
380 report.push('\n');
381
382 if self.error.context().context_len() > 0 {
384 report.push_str("📊 上下文信息:\n");
385 for (key, value) in self.error.context().all_context() {
386 report.push_str(&format!(" {key}: {value}\n"));
387 }
388 report.push('\n');
389 }
390
391 if let Some(timestamp) = self.error.context().timestamp() {
393 report.push_str(&format!("⏰ 发生时间: {timestamp:?}\n"));
394 }
395 report
396 }
397
398 pub fn log_summary(&self) -> String {
400 format!(
401 "Error[{:?}:{:?}] {} - {}",
402 self.error.error_type(),
403 self.error.error_code(),
404 self.error.user_message().unwrap_or("未知错误"),
405 if self.error.is_retryable() {
406 "(可重试)"
407 } else {
408 "(不可重试)"
409 }
410 )
411 }
412
413 pub fn user_friendly_with_suggestion(&self) -> String {
415 format!(
416 "{}\n\n💡 建议: {}\n\n🔧 可以尝试:\n{}",
417 self.error.user_message().unwrap_or("未知错误"),
418 self.error.suggestion(),
419 self.error
420 .recovery_steps()
421 .iter()
422 .enumerate()
423 .map(|(i, step)| format!("{}. {}", i + 1, step))
424 .collect::<Vec<_>>()
425 .join("\n")
426 )
427 }
428}
429
430#[cfg(test)]
438#[allow(unused_imports)]
439mod tests {
440 use super::*;
441
442 #[test]
443 fn test_error_convenience_functions() {
444 let network_err = network_error("连接失败");
445 assert!(network_err.is_network_error());
446 assert!(network_err.is_retryable());
447
448 let auth_err = authentication_error("令牌无效");
449 assert!(auth_err.is_auth_error());
450 assert!(!auth_err.is_retryable());
451
452 let validation_err = validation_error("email", "邮箱格式不正确");
453 assert!(validation_err.is_validation_error());
454 assert!(!validation_err.is_retryable());
455 }
456
457 #[test]
458 fn test_error_analyzer() {
459 let error = api_error(404, "/users", "用户不存在", Some("req-123".to_string()));
460 let analyzer = ErrorAnalyzer::new(&error);
461
462 let report = analyzer.detailed_report();
463 assert!(report.contains("错误分析报告"));
464 assert!(report.contains("API错误"));
465 assert!(report.contains("req-123"));
466
467 let summary = analyzer.log_summary();
468 assert!(summary.contains("Error"));
469 assert!(summary.contains("Api"));
470
471 let user_msg = analyzer.user_friendly_with_suggestion();
472 assert!(user_msg.contains("建议"));
473 assert!(user_msg.contains("可以尝试"));
474 }
475
476 #[test]
477 fn test_client_error_ext() {
478 let error = timeout_error("数据同步");
479
480 assert!(!error.is_network_error());
481 assert!(!error.is_auth_error());
482 assert!(!error.is_business_error());
483 assert!(error.is_retryable());
484
485 let suggestion = error.suggestion();
486 assert!(!suggestion.is_empty());
487
488 let steps = error.recovery_steps();
489 assert!(!steps.is_empty());
490 assert!(steps.contains(&"增加请求超时时间"));
491 }
492
493 #[test]
494 fn test_error_conversions() {
495 let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
497 let error: Error = json_err.into();
498 assert!(error.is_serialization_error());
499
500 }
506
507 #[test]
508 fn test_context_functions() {
509 let result: Result<i32> = Err(validation_error("age", "年龄不能为负数"));
510
511 let contextual_result = with_context(result, "user_id", "12345");
512 assert!(contextual_result.is_err());
513
514 let error = contextual_result.unwrap_err();
515 assert_eq!(error.context().get_context("user_id"), Some("12345"));
518 }
519
520 #[test]
521 fn test_sdk_result_conversion() {
522 let core_result: openlark_core::SDKResult<String> = Ok("success".to_string());
524 let client_result: Result<String> = from_sdk_result(core_result);
525 assert!(client_result.is_ok());
526 assert_eq!(client_result.unwrap(), "success");
527
528 let core_result: openlark_core::SDKResult<String> = Err(network_error("网络错误"));
530 let client_result: Result<String> = from_sdk_result(core_result);
531 assert!(client_result.is_err());
532 assert!(client_result.unwrap_err().is_network_error());
533 }
534
535 #[test]
536 fn test_api_error_function() {
537 let error = api_error(
538 500,
539 "/api/test",
540 "服务器内部错误",
541 Some("req-456".to_string()),
542 );
543 assert!(error.is_api_error());
544 let analyzer = ErrorAnalyzer::new(&error);
545 let report = analyzer.detailed_report();
546 assert!(report.contains("服务器内部错误"));
547 }
548
549 #[test]
550 fn test_validation_error_function() {
551 let error = validation_error("field_name", "字段值为空");
552 assert!(error.is_validation_error());
553 let analyzer = ErrorAnalyzer::new(&error);
554 let user_msg = analyzer.user_friendly_with_suggestion();
555 assert!(user_msg.contains("建议"));
556 }
557
558 #[test]
559 fn test_configuration_error_function() {
560 let error = configuration_error("配置文件缺失");
561 assert!(error.is_config_error());
562 }
563
564 #[test]
565 fn test_serialization_error_function() {
566 let error = serialization_error("JSON解析失败");
567 assert!(error.is_serialization_error());
568 }
569
570 #[test]
571 fn test_business_error_function() {
572 let error = business_error("ERR_001", "业务规则验证失败");
573 assert!(error.is_business_error());
574 }
575
576 #[test]
577 fn test_timeout_error_function() {
578 let error = timeout_error("数据库查询超时");
579 assert!(error.is_timeout_error());
580 assert!(error.is_retryable());
581 }
582
583 #[test]
584 fn test_rate_limit_error_function() {
585 let error = rate_limit_error(Some(60));
586 assert!(error.is_rate_limited());
587 }
588
589 #[test]
590 fn test_service_unavailable_error_function() {
591 let error = service_unavailable_error("支付服务");
592 assert!(error.is_service_unavailable_error());
593 }
594
595 #[test]
596 fn test_internal_error_function() {
597 let error = internal_error("系统内部错误");
598 assert!(!error.is_user_error());
599 }
600
601 #[test]
602 fn test_token_invalid_error_function() {
603 let error = token_invalid_error("token格式不正确");
604 assert!(error.is_auth_error());
605 }
606
607 #[test]
608 fn test_token_expired_error_function() {
609 let error = token_expired_error("token已过期");
610 assert!(error.is_auth_error());
611 }
612
613 #[test]
614 fn test_permission_missing_error_function() {
615 let scopes = vec!["read:user", "write:docs"];
616 let error = permission_missing_error(&scopes);
617 assert!(error.is_auth_error());
618 }
619
620 #[test]
621 fn test_sso_token_invalid_error_function() {
622 let error = sso_token_invalid_error("SSO令牌无效");
623 assert!(error.is_auth_error());
624 }
625
626 #[test]
627 fn test_user_identity_invalid_error_function() {
628 let error = user_identity_invalid_error("用户身份标识非法");
629 assert!(error.is_auth_error());
630 }
631
632 #[test]
633 fn test_from_feishu_response_function() {
634 let error = from_feishu_response(
635 99991677,
636 "/api/test",
637 "token过期",
638 Some("req-789".to_string()),
639 );
640 assert!(!error.to_string().is_empty());
642 let error2 = from_feishu_response(400, "/api/test", "参数错误", None);
643 assert!(!error2.to_string().is_empty());
644 }
645
646 #[test]
647 fn test_registry_error_conversion() {
648 let registry_err = crate::registry::RegistryError::ServiceNotFound {
649 name: "test".to_string(),
650 };
651 let error: Error = registry_err.into();
652 assert!(!error.is_user_error());
653 }
654
655 #[test]
656 fn test_error_analyzer_log_summary() {
657 let error = network_error("连接超时");
658 let analyzer = ErrorAnalyzer::new(&error);
659 let summary = analyzer.log_summary();
660 assert!(summary.contains("Network"));
661 assert!(summary.contains("可重试") || summary.contains("不可重试"));
662 }
663
664 #[test]
665 fn test_error_analyzer_user_friendly() {
666 let error = api_error(404, "/users/123", "用户不存在", None);
667 let analyzer = ErrorAnalyzer::new(&error);
668 let friendly = analyzer.user_friendly_with_suggestion();
669 assert!(friendly.contains("建议"));
670 assert!(friendly.contains("可以尝试"));
671 }
672
673 #[test]
674 fn test_with_operation_context() {
675 let result: Result<i32> = Err(network_error("网络错误"));
676 let contextual_result = with_operation_context(result, "test_operation", "TestComponent");
677 assert!(contextual_result.is_err());
678 let error = contextual_result.unwrap_err();
679 assert_eq!(
680 error.context().get_context("operation"),
681 Some("test_operation")
682 );
683 assert_eq!(
684 error.context().get_context("component"),
685 Some("TestComponent")
686 );
687 }
688
689 #[test]
690 fn test_with_operation_context_updates_timeout_operation_field() {
691 use std::time::Duration;
692
693 let result: Result<i32> = Err(openlark_core::error::timeout_error(
694 Duration::from_secs(3),
695 Some("old_operation".to_string()),
696 ));
697
698 let contextual_result = with_operation_context(result, "new_operation", "ClientLayer");
699 assert!(contextual_result.is_err());
700
701 match contextual_result.unwrap_err() {
702 CoreError::Timeout {
703 operation, ref ctx, ..
704 } => {
705 assert_eq!(operation.as_deref(), Some("new_operation"));
706 assert_eq!(ctx.operation(), Some("new_operation"));
707 assert_eq!(ctx.component(), Some("ClientLayer"));
708 }
709 other => panic!("expected timeout error, got {:?}", other.error_type()),
710 }
711 }
712
713 #[test]
714 fn test_all_error_types_suggestion() {
715 let error_types = vec![
716 (network_error("test"), "检查网络连接"),
717 (authentication_error("test"), "验证应用凭据"),
718 (api_error(500, "/test", "test", None), "检查API参数"),
719 (validation_error("field", "test"), "验证输入参数"),
720 (configuration_error("test"), "检查应用配置"),
721 (serialization_error("test"), "确认数据格式"),
722 (business_error("code", "test"), "确认业务逻辑"),
723 (timeout_error("test"), "增加超时时间"),
724 (rate_limit_error(None), "稍后重试"),
725 (service_unavailable_error("svc"), "稍后重试"),
726 (internal_error("test"), "联系技术支持"),
727 ];
728
729 for (error, expected_keyword) in error_types {
730 let suggestion = error.suggestion();
731 assert!(
732 suggestion.contains(expected_keyword) || !suggestion.is_empty(),
733 "Error type {:?} should have meaningful suggestion",
734 error.error_type()
735 );
736 }
737 }
738
739 #[test]
740 fn test_all_error_types_recovery_steps() {
741 let error_types = vec![
742 network_error("test"),
743 authentication_error("test"),
744 api_error(500, "/test", "test", None),
745 validation_error("field", "test"),
746 configuration_error("test"),
747 serialization_error("test"),
748 business_error("code", "test"),
749 timeout_error("test"),
750 rate_limit_error(None),
751 service_unavailable_error("svc"),
752 internal_error("test"),
753 ];
754
755 for error in error_types {
756 let steps = error.recovery_steps();
757 assert!(
758 !steps.is_empty(),
759 "Error type {:?} should have recovery steps",
760 error.error_type()
761 );
762 assert!(
763 steps.len() >= 3,
764 "Error type {:?} should have at least 3 recovery steps",
765 error.error_type()
766 );
767 }
768 }
769}