Skip to main content

what_core/
error.rs

1//! Error types for what-core
2
3use thiserror::Error;
4
5/// Result type alias for What operations
6pub type Result<T> = std::result::Result<T, Error>;
7
8/// Main error type for what-core
9#[derive(Error, Debug)]
10pub enum Error {
11    #[error("Configuration error: {0}")]
12    Config(String),
13
14    #[error("Parse error: {0}")]
15    Parse(String),
16
17    #[error("Component error: {0}")]
18    Component(String),
19
20    #[error("Template error: {0}")]
21    Template(String),
22
23    #[error("Data error: {0}")]
24    Data(String),
25
26    #[error("Cache error: {0}")]
27    Cache(String),
28
29    #[error("Action error: {0}")]
30    Action(String),
31
32    #[error("Server error: {0}")]
33    Server(String),
34
35    #[error("IO error: {0}")]
36    Io(#[from] std::io::Error),
37
38    #[error("HTTP error: {0}")]
39    Http(#[from] reqwest::Error),
40
41    #[error("JSON error: {0}")]
42    Json(#[from] serde_json::Error),
43
44    #[error("TOML error: {0}")]
45    Toml(#[from] toml::de::Error),
46
47    #[error("Session error: {0}")]
48    Session(String),
49
50    #[error("SQLite error: {0}")]
51    Sqlite(#[from] rusqlite::Error),
52
53    #[error("Authentication error: {0}")]
54    Auth(String),
55
56    #[error("JWT error: {0}")]
57    Jwt(#[from] jsonwebtoken::errors::Error),
58
59    #[error("Upload error: {0}")]
60    Upload(String),
61
62    #[error("Validation error: {0}")]
63    Validation(String),
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    // --- Display formatting tests ---
71
72    #[test]
73    fn test_config_error_display() {
74        let err = Error::Config("missing field".to_string());
75        assert_eq!(err.to_string(), "Configuration error: missing field");
76    }
77
78    #[test]
79    fn test_parse_error_display() {
80        let err = Error::Parse("unexpected token".to_string());
81        assert_eq!(err.to_string(), "Parse error: unexpected token");
82    }
83
84    #[test]
85    fn test_component_error_display() {
86        let err = Error::Component("slot not found".to_string());
87        assert_eq!(err.to_string(), "Component error: slot not found");
88    }
89
90    #[test]
91    fn test_template_error_display() {
92        let err = Error::Template("invalid syntax".to_string());
93        assert_eq!(err.to_string(), "Template error: invalid syntax");
94    }
95
96    #[test]
97    fn test_data_error_display() {
98        let err = Error::Data("source unavailable".to_string());
99        assert_eq!(err.to_string(), "Data error: source unavailable");
100    }
101
102    #[test]
103    fn test_cache_error_display() {
104        let err = Error::Cache("connection lost".to_string());
105        assert_eq!(err.to_string(), "Cache error: connection lost");
106    }
107
108    #[test]
109    fn test_action_error_display() {
110        let err = Error::Action("handler failed".to_string());
111        assert_eq!(err.to_string(), "Action error: handler failed");
112    }
113
114    #[test]
115    fn test_server_error_display() {
116        let err = Error::Server("bind failed".to_string());
117        assert_eq!(err.to_string(), "Server error: bind failed");
118    }
119
120    #[test]
121    fn test_session_error_display() {
122        let err = Error::Session("expired".to_string());
123        assert_eq!(err.to_string(), "Session error: expired");
124    }
125
126    #[test]
127    fn test_auth_error_display() {
128        let err = Error::Auth("unauthorized".to_string());
129        assert_eq!(err.to_string(), "Authentication error: unauthorized");
130    }
131
132    // --- From conversion tests ---
133
134    #[test]
135    fn test_from_io_error() {
136        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
137        let err: Error = Error::from(io_err);
138        match &err {
139            Error::Io(_) => {}
140            _ => panic!("Expected Error::Io variant, got: {:?}", err),
141        }
142        assert!(err.to_string().contains("file not found"));
143    }
144
145    #[test]
146    fn test_from_json_error() {
147        let json_result: std::result::Result<serde_json::Value, _> =
148            serde_json::from_str("not json");
149        let json_err = json_result.unwrap_err();
150        let err: Error = Error::from(json_err);
151        match &err {
152            Error::Json(_) => {}
153            _ => panic!("Expected Error::Json variant, got: {:?}", err),
154        }
155        assert!(err.to_string().starts_with("JSON error:"));
156    }
157
158    #[test]
159    fn test_from_toml_error() {
160        let toml_result: std::result::Result<toml::Value, _> = toml::from_str("not valid [[[toml");
161        let toml_err = toml_result.unwrap_err();
162        let err: Error = Error::from(toml_err);
163        match &err {
164            Error::Toml(_) => {}
165            _ => panic!("Expected Error::Toml variant, got: {:?}", err),
166        }
167        assert!(err.to_string().starts_with("TOML error:"));
168    }
169
170    #[test]
171    fn test_from_rusqlite_error() {
172        let sqlite_err = rusqlite::Error::InvalidParameterName("bad_param".to_string());
173        let err: Error = Error::from(sqlite_err);
174        match &err {
175            Error::Sqlite(_) => {}
176            _ => panic!("Expected Error::Sqlite variant, got: {:?}", err),
177        }
178        assert!(err.to_string().starts_with("SQLite error:"));
179    }
180
181    #[test]
182    fn test_from_jwt_error() {
183        // Decode an invalid token to produce a JWT error
184        let jwt_result = jsonwebtoken::decode::<serde_json::Value>(
185            "not.a.token",
186            &jsonwebtoken::DecodingKey::from_secret(b"secret"),
187            &jsonwebtoken::Validation::default(),
188        );
189        let jwt_err = jwt_result.unwrap_err();
190        let err: Error = Error::from(jwt_err);
191        match &err {
192            Error::Jwt(_) => {}
193            _ => panic!("Expected Error::Jwt variant, got: {:?}", err),
194        }
195        assert!(err.to_string().starts_with("JWT error:"));
196    }
197
198    // --- Result type alias test ---
199
200    #[test]
201    fn test_result_type_ok() {
202        let result: Result<i32> = Ok(42);
203        assert_eq!(result.unwrap(), 42);
204    }
205
206    #[test]
207    fn test_result_type_err() {
208        let result: Result<i32> = Err(Error::Config("test".to_string()));
209        assert!(result.is_err());
210    }
211
212    // --- Debug trait test ---
213
214    #[test]
215    fn test_error_is_debuggable() {
216        let err = Error::Config("test error".to_string());
217        let debug_str = format!("{:?}", err);
218        assert!(debug_str.contains("Config"));
219        assert!(debug_str.contains("test error"));
220    }
221
222    // --- Error with empty message ---
223
224    #[test]
225    fn test_error_with_empty_message() {
226        let err = Error::Parse(String::new());
227        assert_eq!(err.to_string(), "Parse error: ");
228    }
229
230    // --- Question mark operator with From conversions ---
231
232    #[test]
233    fn test_io_error_via_question_mark() {
234        fn may_fail() -> Result<()> {
235            let _file = std::fs::File::open("/nonexistent/file/path")?;
236            Ok(())
237        }
238        let result = may_fail();
239        assert!(result.is_err());
240        match result.unwrap_err() {
241            Error::Io(_) => {}
242            other => panic!("Expected Error::Io, got: {:?}", other),
243        }
244    }
245}