1use std::time::Duration;
2
3use crate::core::{
4 api_resp::BaseResponse,
5 error::LarkAPIError,
6 error_codes::{ErrorCategory, LarkErrorCode},
7};
8
9pub struct ErrorHelper;
11
12impl ErrorHelper {
13 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 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 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 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 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 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 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#[derive(Debug, Clone)]
234pub struct ErrorHandlingAdvice {
235 pub message: String,
237 pub category: ErrorHandlingCategory,
239 pub error_code: Option<LarkErrorCode>,
241 pub is_recoverable: bool,
243 pub is_retryable: bool,
245 pub retry_delay: Option<u64>,
247 pub actions: Vec<String>,
249 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
270pub enum ErrorHandlingCategory {
271 Authentication,
273 Permission,
275 ClientError,
277 ServerError,
279 NetworkError,
281 RateLimit,
283 SystemError,
285 Unknown,
287}
288
289#[derive(Debug, Clone)]
291pub struct RetryStrategy {
292 pub max_attempts: u32,
294 pub base_delay: Duration,
296 pub use_exponential_backoff: bool,
298 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 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#[derive(Debug, Clone)]
328pub struct ErrorContext {
329 pub error_message: String,
331 pub user_friendly_message: String,
333 pub category: ErrorHandlingCategory,
335 pub is_recoverable: bool,
337 pub is_retryable: bool,
339 pub suggested_actions: Vec<String>,
341 pub help_url: Option<String>,
343 pub retry_strategy: Option<RetryStrategy>,
345}
346
347impl ErrorContext {
348 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 use rstest::rstest;
384
385 #[test]
386 fn test_error_helper_api_error() {
387 let error = LarkAPIError::api_error(403, "Forbidden", None);
388 let advice = ErrorHelper::handle_error(&error);
389
390 assert_eq!(advice.category, ErrorHandlingCategory::Permission);
391 assert!(advice.is_recoverable);
392 assert!(!advice.actions.is_empty());
393 }
394
395 #[test]
396 fn test_retry_strategy() {
397 let error = LarkAPIError::api_error(429, "Too Many Requests", None);
398 let strategy = ErrorHelper::create_retry_strategy(&error);
399
400 assert!(strategy.is_some());
401 let strategy = strategy.unwrap();
402 assert_eq!(strategy.max_attempts, 3);
403 }
404
405 #[test]
406 fn test_error_context() {
407 let error = LarkAPIError::MissingAccessToken;
408 let context = ErrorHelper::create_error_context(&error);
409
410 assert_eq!(context.category, ErrorHandlingCategory::Authentication);
411 assert!(context.is_recoverable);
412 }
413
414 #[rstest]
417 #[case(
418 LarkAPIError::MissingAccessToken,
419 ErrorHandlingCategory::Authentication,
420 true,
421 false
422 )]
423 #[case(LarkAPIError::IllegalParamError("invalid param".to_string()), ErrorHandlingCategory::ClientError, true, false)]
424 fn test_handle_error_various_types(
425 #[case] error: LarkAPIError,
426 #[case] expected_category: ErrorHandlingCategory,
427 #[case] expected_recoverable: bool,
428 #[case] expected_retryable: bool,
429 ) {
430 let advice = ErrorHelper::handle_error(&error);
431
432 assert_eq!(advice.category, expected_category);
433 assert_eq!(advice.is_recoverable, expected_recoverable);
434 assert_eq!(advice.is_retryable, expected_retryable);
435 assert!(!advice.message.is_empty());
436 }
437
438 #[rstest]
439 #[case("timeout error", true, Some(5))]
440 #[case("connection failed", true, Some(10))]
441 #[case("invalid request", false, None)]
442 #[case("other network error", false, None)]
443 fn test_handle_request_error(
444 #[case] error_msg: &str,
445 #[case] expected_retryable: bool,
446 #[case] expected_delay: Option<u64>,
447 ) {
448 let advice = ErrorHelper::handle_request_error(error_msg);
449
450 assert_eq!(advice.category, ErrorHandlingCategory::NetworkError);
451 assert!(advice.is_recoverable);
452 assert_eq!(advice.is_retryable, expected_retryable);
453 assert_eq!(advice.retry_delay, expected_delay);
454 assert!(!advice.actions.is_empty());
455 }
456
457 #[test]
458 fn test_handle_api_error_with_different_error_codes() {
459 let error_code = LarkErrorCode::AccessTokenInvalid; let advice = ErrorHelper::handle_api_error(error_code, "Invalid token");
462
463 assert_eq!(advice.category, ErrorHandlingCategory::Authentication);
464 assert!(advice.is_recoverable);
465 assert!(!advice.actions.is_empty());
466 assert!(advice.actions.iter().any(|a| a.contains("访问令牌")));
467
468 let error_code = LarkErrorCode::TooManyRequests;
470 let advice = ErrorHelper::handle_api_error(error_code, "Rate limited");
471
472 assert_eq!(advice.category, ErrorHandlingCategory::RateLimit);
473 assert!(advice.is_recoverable);
474 assert!(advice.is_retryable);
475 assert!(advice.retry_delay.is_some());
476 }
477
478 #[test]
479 fn test_analyze_response_success() {
480 let response: BaseResponse<()> = BaseResponse {
481 raw_response: crate::core::api_resp::RawResponse {
482 code: 0,
483 msg: "ok".to_string(),
484 err: None,
485 },
486 data: None,
487 };
488
489 let advice = ErrorHelper::analyze_response(&response);
490 assert!(advice.is_none());
491 }
492
493 #[test]
494 fn test_analyze_response_error() {
495 let response: BaseResponse<()> = BaseResponse {
496 raw_response: crate::core::api_resp::RawResponse {
497 code: 400,
498 msg: "Bad Request".to_string(),
499 err: None,
500 },
501 data: None,
502 };
503
504 let advice = ErrorHelper::analyze_response(&response);
505 assert!(advice.is_some());
506
507 let advice = advice.unwrap();
508 assert!(!advice.message.is_empty());
509 assert_eq!(advice.category, ErrorHandlingCategory::ClientError);
511 }
512
513 #[test]
514 fn test_create_retry_strategy_various_errors() {
515 let error = LarkAPIError::api_error(429, "Too Many Requests", None);
517 let strategy = ErrorHelper::create_retry_strategy(&error);
518 assert!(strategy.is_some());
519
520 let error = LarkAPIError::IllegalParamError("invalid".to_string());
522 let strategy = ErrorHelper::create_retry_strategy(&error);
523 assert!(strategy.is_none());
524
525 let error = LarkAPIError::RequestError("timeout error".to_string());
527 let strategy = ErrorHelper::create_retry_strategy(&error);
528 assert!(strategy.is_some());
529
530 let strategy = strategy.unwrap();
531 assert_eq!(strategy.max_attempts, 3);
532 assert_eq!(strategy.base_delay, Duration::from_secs(5));
533 }
534
535 #[test]
536 fn test_format_user_error() {
537 let error = LarkAPIError::api_error(403, "Forbidden", None);
539 let message = ErrorHelper::format_user_error(&error);
540 assert!(!message.is_empty());
541
542 let error = LarkAPIError::api_error(99999, "Unknown", None);
544 let message = ErrorHelper::format_user_error(&error);
545 assert!(message.contains("99999"));
546
547 let error = LarkAPIError::MissingAccessToken;
549 let message = ErrorHelper::format_user_error(&error);
550 assert!(!message.is_empty());
551 }
552
553 #[test]
554 fn test_create_error_context_complete() {
555 let error = LarkAPIError::api_error(500, "Internal Server Error", None);
556 let context = ErrorHelper::create_error_context(&error);
557
558 assert!(!context.error_message.is_empty());
559 assert!(!context.user_friendly_message.is_empty());
560 assert!(!context.suggested_actions.is_empty());
561
562 assert!(context.is_retryable);
564 assert!(context.retry_strategy.is_some());
565 }
566
567 #[test]
568 fn test_error_handling_advice_default() {
569 let advice = ErrorHandlingAdvice::default();
570
571 assert!(advice.message.is_empty());
572 assert_eq!(advice.category, ErrorHandlingCategory::Unknown);
573 assert!(advice.error_code.is_none());
574 assert!(!advice.is_recoverable);
575 assert!(!advice.is_retryable);
576 assert!(advice.retry_delay.is_none());
577 assert!(advice.actions.is_empty());
578 assert!(advice.help_url.is_none());
579 }
580
581 #[test]
582 fn test_retry_strategy_default() {
583 let strategy = RetryStrategy::default();
584
585 assert_eq!(strategy.max_attempts, 3);
586 assert_eq!(strategy.base_delay, Duration::from_secs(5));
587 assert!(strategy.use_exponential_backoff);
588 assert_eq!(strategy.max_delay, Duration::from_secs(60));
589 }
590
591 #[test]
592 fn test_retry_strategy_calculate_delay() {
593 let strategy = RetryStrategy::default();
594
595 assert_eq!(strategy.calculate_delay(0), Duration::from_secs(5));
597 assert_eq!(strategy.calculate_delay(1), Duration::from_secs(10));
598 assert_eq!(strategy.calculate_delay(2), Duration::from_secs(20));
599 assert_eq!(strategy.calculate_delay(3), Duration::from_secs(40));
600
601 assert_eq!(strategy.calculate_delay(10), Duration::from_secs(60));
603
604 let strategy = RetryStrategy {
606 use_exponential_backoff: false,
607 ..Default::default()
608 };
609 assert_eq!(strategy.calculate_delay(5), Duration::from_secs(5));
610 }
611
612 #[test]
613 fn test_error_context_print_details() {
614 let context = ErrorContext {
615 error_message: "Original error".to_string(),
616 user_friendly_message: "User friendly error".to_string(),
617 category: ErrorHandlingCategory::NetworkError,
618 is_recoverable: true,
619 is_retryable: true,
620 suggested_actions: vec!["Action 1".to_string(), "Action 2".to_string()],
621 help_url: Some("https://example.com/help".to_string()),
622 retry_strategy: Some(RetryStrategy::default()),
623 };
624
625 context.print_details();
628 }
629
630 #[test]
631 fn test_error_handling_category_equality() {
632 assert_eq!(
633 ErrorHandlingCategory::Authentication,
634 ErrorHandlingCategory::Authentication
635 );
636 assert_ne!(
637 ErrorHandlingCategory::Authentication,
638 ErrorHandlingCategory::Permission
639 );
640 }
641
642 #[test]
643 fn test_unknown_api_error_code() {
644 let error = LarkAPIError::api_error(99999, "Unknown error", None);
645 let advice = ErrorHelper::handle_error(&error);
646
647 assert_eq!(advice.category, ErrorHandlingCategory::Unknown);
648 assert!(advice.message.contains("未知API错误"));
649 assert!(advice.message.contains("99999"));
650 }
651
652 #[test]
653 fn test_request_error_patterns() {
654 let timeout_errors = vec!["request timeout", "connection timed out", "read timeout"];
656
657 for error_msg in timeout_errors {
658 let advice = ErrorHelper::handle_request_error(error_msg);
659 assert!(advice.is_retryable);
660 assert_eq!(advice.retry_delay, Some(5));
661 }
662
663 let connection_errors = vec![
665 "connection refused",
666 "cannot connect to server",
667 "connection reset",
668 ];
669
670 for error_msg in connection_errors {
671 let advice = ErrorHelper::handle_request_error(error_msg);
672 assert!(advice.is_retryable);
673 assert_eq!(advice.retry_delay, Some(10));
674 }
675 }
676}