1use thiserror::Error;
2
3#[derive(Error, Debug)]
45pub enum LarkAPIError {
46 #[error("IO error: {0}")]
50 IOErr(String),
51
52 #[error("Invalid parameter: {0}")]
56 IllegalParamError(String),
57
58 #[error("JSON deserialization error: {0}")]
62 DeserializeError(String),
63
64 #[error("HTTP request failed: {0}")]
68 RequestError(String),
69
70 #[error("URL parse error: {0}")]
74 UrlParseError(String),
75
76 #[error("API error: {message} (code: {code}, request_id: {request_id:?})")]
80 ApiError {
81 code: i32,
83 message: String,
85 request_id: Option<String>,
87 },
88
89 #[error("Missing access token")]
93 MissingAccessToken,
94
95 #[error("Bad request: {0}")]
99 BadRequest(String),
100
101 #[error("Data error: {0}")]
105 DataError(String),
106
107 #[error("API error: {msg} (code: {code})")]
111 APIError {
112 code: i32,
114 msg: String,
116 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
200pub enum ErrorSeverity {
201 Info,
203 Warning,
205 Error,
207 Critical,
209}
210
211impl LarkAPIError {
212 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 pub fn illegal_param<T: Into<String>>(message: T) -> Self {
249 Self::IllegalParamError(message.into())
250 }
251
252 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 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 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 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 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 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 let missing_token = LarkAPIError::MissingAccessToken;
608 assert_eq!(
609 missing_token.user_friendly_message(),
610 "缺少访问令牌,请检查认证配置"
611 );
612
613 let param_error = LarkAPIError::IllegalParamError("invalid format".to_string());
615 assert_eq!(
616 param_error.user_friendly_message(),
617 "参数错误: invalid format"
618 );
619
620 let timeout_error = LarkAPIError::RequestError("connection timeout".to_string());
622 assert_eq!(
623 timeout_error.user_friendly_message(),
624 "请求超时,请检查网络连接"
625 );
626
627 let connect_error = LarkAPIError::RequestError("connection failed".to_string());
629 assert_eq!(
630 connect_error.user_friendly_message(),
631 "连接失败,请检查网络设置"
632 );
633
634 let generic_error = LarkAPIError::RequestError("unknown error".to_string());
636 assert_eq!(
637 generic_error.user_friendly_message(),
638 "网络请求失败: unknown error"
639 );
640
641 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, 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 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}