1use std::time::Duration;
2
3use thiserror::Error;
4
5#[derive(Error, Debug)]
7pub enum WechatIlinkError {
8 #[error("API error: {message} (http={http_status}, errcode={errcode})")]
9 Api {
10 message: String,
11 http_status: u16,
12 errcode: i32,
13 },
14
15 #[error("Rate limited: {message} (http={http_status}, errcode={errcode}, retry_after={}s)", retry_after.as_secs())]
16 RateLimited {
17 retry_after: Duration,
18 message: String,
19 http_status: u16,
20 errcode: i32,
21 },
22
23 #[error("Auth error: {0}")]
24 Auth(String),
25
26 #[error("No context_token for user {0}")]
27 NoContext(String),
28
29 #[error("Media error: {0}")]
30 Media(String),
31
32 #[error("Transport error: {0}")]
33 Transport(#[from] reqwest::Error),
34
35 #[error("JSON error: {0}")]
36 Json(#[from] serde_json::Error),
37
38 #[error("IO error: {0}")]
39 Io(#[from] std::io::Error),
40
41 #[error("{0}")]
42 Other(String),
43}
44
45impl WechatIlinkError {
46 pub fn is_session_expired(&self) -> bool {
48 matches!(self, WechatIlinkError::Api { errcode: -14, .. })
49 }
50
51 pub fn is_rate_limited(&self) -> bool {
53 matches!(self, WechatIlinkError::RateLimited { .. })
54 }
55
56 pub fn retry_after(&self) -> Option<Duration> {
58 match self {
59 WechatIlinkError::RateLimited { retry_after, .. } => Some(*retry_after),
60 _ => None,
61 }
62 }
63}
64
65pub type Result<T> = std::result::Result<T, WechatIlinkError>;
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn session_expired_true() {
73 let err = WechatIlinkError::Api {
74 message: "session expired".to_string(),
75 http_status: 200,
76 errcode: -14,
77 };
78 assert!(err.is_session_expired());
79 }
80
81 #[test]
82 fn session_expired_false() {
83 let err = WechatIlinkError::Api {
84 message: "other error".to_string(),
85 http_status: 400,
86 errcode: -1,
87 };
88 assert!(!err.is_session_expired());
89 }
90
91 #[test]
92 fn non_api_not_session_expired() {
93 let err = WechatIlinkError::Auth("test".to_string());
94 assert!(!err.is_session_expired());
95 }
96
97 #[test]
98 fn error_display() {
99 let err = WechatIlinkError::Api {
100 message: "bad request".to_string(),
101 http_status: 400,
102 errcode: -1,
103 };
104 let msg = format!("{}", err);
105 assert!(msg.contains("bad request"));
106 assert!(msg.contains("400"));
107 assert!(msg.contains("-1"));
108 }
109
110 #[test]
111 fn no_context_error() {
112 let err = WechatIlinkError::NoContext("user123".to_string());
113 let msg = format!("{}", err);
114 assert!(msg.contains("user123"));
115 }
116}