Skip to main content

wechat_ilink/
error.rs

1use std::time::Duration;
2
3use thiserror::Error;
4
5/// Errors that can occur in the SDK.
6#[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    /// Returns true if this is a session-expired error (errcode -14).
47    pub fn is_session_expired(&self) -> bool {
48        matches!(self, WechatIlinkError::Api { errcode: -14, .. })
49    }
50
51    /// Returns true if this is a bot-wide iLink rate limit response (ret/errcode -2).
52    pub fn is_rate_limited(&self) -> bool {
53        matches!(self, WechatIlinkError::RateLimited { .. })
54    }
55
56    /// Returns the suggested delay before retrying a rate-limited request.
57    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}