Skip to main content

wechat_agent_rs/
errors.rs

1use snafu::Snafu;
2
3/// Errors that can occur when interacting with the `WeChat` Agent SDK.
4#[derive(Debug, Snafu)]
5#[snafu(visibility(pub))]
6pub enum Error {
7    /// An HTTP request failed.
8    #[snafu(display("HTTP error: {source}"))]
9    Http { source: reqwest::Error },
10
11    /// JSON serialization or deserialization failed.
12    #[snafu(display("JSON error: {source}"))]
13    Json { source: serde_json::Error },
14
15    /// A filesystem I/O operation failed.
16    #[snafu(display("IO error: {source}"))]
17    Io { source: std::io::Error },
18
19    /// The `WeChat` API returned a non-zero error code.
20    #[snafu(display("API error (code {code}): {message}"))]
21    Api {
22        /// The numeric error code from the API.
23        code:    i64,
24        /// The human-readable error message.
25        message: String,
26    },
27
28    /// The current session has expired and requires re-authentication.
29    #[snafu(display("Session expired"))]
30    SessionExpired,
31
32    /// The login QR code has expired before being scanned.
33    #[snafu(display("QR code expired"))]
34    QrCodeExpired,
35
36    /// The login flow failed for the given reason.
37    #[snafu(display("Login failed: {reason}"))]
38    LoginFailed {
39        /// Description of why the login failed.
40        reason: String,
41    },
42
43    /// No saved account was found in local storage.
44    #[snafu(display("No account found"))]
45    NoAccount,
46
47    /// An AES encryption or decryption operation failed.
48    #[snafu(display("Encryption error: {reason}"))]
49    Encryption {
50        /// Description of the encryption failure.
51        reason: String,
52    },
53}
54
55/// A convenience alias for `Result<T, Error>`.
56pub type Result<T> = std::result::Result<T, Error>;
57
58#[cfg(test)]
59mod tests {
60    use snafu::IntoError;
61
62    use super::*;
63
64    #[test]
65    fn test_error_display_api() {
66        let err = ApiSnafu {
67            code:    42_i64,
68            message: "bad request".to_string(),
69        }
70        .build();
71        let display = format!("{err}");
72        assert!(
73            display.contains("42"),
74            "should contain error code, got: {display}"
75        );
76        assert!(
77            display.contains("bad request"),
78            "should contain message, got: {display}"
79        );
80    }
81
82    #[test]
83    fn test_error_display_login_failed() {
84        let err = LoginFailedSnafu {
85            reason: "invalid credentials".to_string(),
86        }
87        .build();
88        let display = format!("{err}");
89        assert!(
90            display.contains("invalid credentials"),
91            "should contain reason, got: {display}"
92        );
93    }
94
95    #[test]
96    fn test_error_display_encryption() {
97        let err = EncryptionSnafu {
98            reason: "bad key".to_string(),
99        }
100        .build();
101        let display = format!("{err}");
102        assert!(
103            display.contains("bad key"),
104            "should contain reason, got: {display}"
105        );
106    }
107
108    #[test]
109    fn test_error_from_io() {
110        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
111        let err: Error = IoSnafu.into_error(io_err);
112        assert!(
113            matches!(err, Error::Io { .. }),
114            "expected Io variant, got: {err:?}"
115        );
116        let display = format!("{err}");
117        assert!(
118            display.contains("file missing"),
119            "should contain source message, got: {display}"
120        );
121    }
122
123    #[test]
124    fn test_error_from_json() {
125        let json_err = serde_json::from_str::<serde_json::Value>("not json").unwrap_err();
126        let err: Error = JsonSnafu.into_error(json_err);
127        assert!(
128            matches!(err, Error::Json { .. }),
129            "expected Json variant, got: {err:?}"
130        );
131    }
132}