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
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}