1use thiserror::Error;
4
5#[derive(Error, Debug)]
11#[non_exhaustive]
12pub enum RavenClawsError {
13 #[error("LLM error: {0}")]
14 Llm(#[from] crate::llm::LLMError),
15
16 #[error("Configuration error: {0}")]
17 Config(#[from] crate::config::ConfigError),
18
19 #[error("RavenFabric error: {0}")]
20 #[allow(dead_code)]
21 RavenFabric(String),
22
23 #[error("Network error: {0}")]
24 Network(#[from] reqwest::Error),
25
26 #[error("IO error: {0}")]
27 IO(#[from] std::io::Error),
28
29 #[error("Command execution failed: {0}")]
30 CommandExecution(String),
31
32 #[error("Security violation: {0}")]
33 #[allow(dead_code)]
34 SecurityViolation(String),
35}
36
37pub type Result<T> = std::result::Result<T, RavenClawsError>;
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42
43 #[test]
44 fn test_llm_error_variant() {
45 let err = RavenClawsError::Llm(crate::llm::LLMError::RequestFailed("timeout".to_string()));
46 assert_eq!(format!("{}", err), "LLM error: Request failed: timeout");
47 }
48
49 #[test]
50 fn test_config_error_variant() {
51 let err = RavenClawsError::Config(crate::config::ConfigError::ValidationError(
52 "bad field".to_string(),
53 ));
54 assert_eq!(
55 format!("{}", err),
56 "Configuration error: Invalid configuration: bad field"
57 );
58 }
59
60 #[test]
61 fn test_ravenfabric_error_variant() {
62 let err = RavenClawsError::RavenFabric("connection refused".to_string());
63 assert_eq!(format!("{}", err), "RavenFabric error: connection refused");
64 }
65
66 #[test]
67 fn test_command_execution_error_variant() {
68 let err = RavenClawsError::CommandExecution("command failed".to_string());
69 assert_eq!(
70 format!("{}", err),
71 "Command execution failed: command failed"
72 );
73 }
74
75 #[test]
76 fn test_security_violation_error_variant() {
77 let err = RavenClawsError::SecurityViolation("unauthorized access".to_string());
78 assert_eq!(
79 format!("{}", err),
80 "Security violation: unauthorized access"
81 );
82 }
83
84 #[test]
85 fn test_result_type_alias() {
86 let ok: i32 = 42;
87 assert_eq!(ok, 42);
88
89 let err: Result<i32> = Err(RavenClawsError::CommandExecution("fail".to_string()));
90 assert!(err.is_err());
91 }
92
93 #[tokio::test]
94 async fn test_network_error_variant() {
95 let err = RavenClawsError::Network(
99 reqwest::Client::builder()
100 .build()
101 .unwrap()
102 .get("http://invalid.example.com")
103 .send()
104 .await
105 .unwrap_err(),
106 );
107 assert!(format!("{}", err).contains("Network error"));
108 }
109
110 #[test]
111 fn test_io_error_variant() {
112 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
113 let err = RavenClawsError::IO(io_err);
114 assert!(format!("{}", err).contains("IO error"));
115 assert!(format!("{}", err).contains("file not found"));
116 }
117
118 #[test]
119 fn test_error_is_debug() {
120 let err = RavenClawsError::CommandExecution("test".to_string());
121 let debug = format!("{:?}", err);
122 assert!(debug.contains("CommandExecution"));
123 }
124
125 #[test]
126 fn test_error_is_send() {
127 fn check_send<T: Send>() {}
128 check_send::<RavenClawsError>();
129 }
130
131 #[test]
132 fn test_error_is_sync() {
133 fn check_sync<T: Sync>() {}
134 check_sync::<RavenClawsError>();
135 }
136
137 #[test]
138 fn test_from_llm_error_conversion() {
139 let llm_err = crate::llm::LLMError::RequestFailed("timeout".to_string());
140 let err: RavenClawsError = llm_err.into();
141 assert!(format!("{}", err).contains("LLM error"));
142 assert!(format!("{}", err).contains("timeout"));
143 }
144
145 #[test]
146 fn test_from_config_error_conversion() {
147 let cfg_err = crate::config::ConfigError::ValidationError("bad config".to_string());
148 let err: RavenClawsError = cfg_err.into();
149 assert!(format!("{}", err).contains("Configuration error"));
150 assert!(format!("{}", err).contains("bad config"));
151 }
152
153 #[test]
154 fn test_from_io_error_conversion() {
155 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "permission denied");
156 let err: RavenClawsError = io_err.into();
157 assert!(format!("{}", err).contains("IO error"));
158 assert!(format!("{}", err).contains("permission denied"));
159 }
160
161 #[test]
162 fn test_error_source_chain() {
163 let inner = crate::llm::LLMError::AuthFailed;
166 let err = RavenClawsError::Llm(inner);
167 let display = format!("{}", err);
168 assert!(display.contains("Authentication failed"));
169 }
170
171 #[test]
172 fn test_ravenfabric_error_construction() {
173 let err = RavenClawsError::RavenFabric("connection timeout".to_string());
174 assert_eq!(format!("{}", err), "RavenFabric error: connection timeout");
175 }
176
177 #[test]
178 fn test_security_violation_construction() {
179 let err = RavenClawsError::SecurityViolation("invalid token".to_string());
180 assert_eq!(format!("{}", err), "Security violation: invalid token");
181 }
182
183 #[test]
184 #[allow(clippy::unnecessary_literal_unwrap)]
185 fn test_result_type_alias_ok() {
186 let result: Result<i32> = Ok(42);
187 assert!(result.is_ok());
188 assert_eq!(result.unwrap(), 42);
189 }
190
191 #[test]
192 #[allow(clippy::unnecessary_literal_unwrap)]
193 fn test_result_type_alias_err() {
194 let result: Result<i32> = Err(RavenClawsError::CommandExecution("fail".to_string()));
195 assert!(result.is_err());
196 assert_eq!(
197 format!("{}", result.unwrap_err()),
198 "Command execution failed: fail"
199 );
200 }
201
202 #[test]
203 fn test_error_into_boxed() {
204 let err = RavenClawsError::CommandExecution("boxed".to_string());
206 let boxed: Box<dyn std::error::Error> = Box::new(err);
207 assert!(format!("{}", boxed).contains("Command execution failed"));
208 }
209
210 #[test]
211 fn test_error_into_string() {
212 let err = RavenClawsError::SecurityViolation("access denied".to_string());
213 let msg: String = err.to_string();
214 assert_eq!(msg, "Security violation: access denied");
215 }
216
217 #[test]
218 fn test_error_from_reqwest() {
219 fn _check_from()
223 where
224 reqwest::Error: Into<RavenClawsError>,
225 {
226 }
227 }
229
230 #[test]
231 fn test_error_display_network_variant() {
232 let rt = tokio::runtime::Runtime::new().unwrap();
234 let err = rt.block_on(async {
235 reqwest::Client::builder()
236 .build()
237 .unwrap()
238 .get("http://invalid.example.com")
239 .send()
240 .await
241 .unwrap_err()
242 });
243 let raven_err = RavenClawsError::Network(err);
244 let display = format!("{}", raven_err);
245 assert!(display.contains("Network error"));
246 assert!(!display.is_empty());
247 }
248
249 #[test]
250 fn test_error_source_chain_io() {
251 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
253 let err = RavenClawsError::IO(io_err);
254 let display = format!("{}", err);
255 assert!(display.contains("IO error"));
256 assert!(display.contains("file not found"));
257 }
258
259 #[test]
260 fn test_error_source_chain_config() {
261 let cfg_err = crate::config::ConfigError::ValidationError("invalid".to_string());
262 let err = RavenClawsError::Config(cfg_err);
263 let display = format!("{}", err);
264 assert!(display.contains("Configuration error"));
265 assert!(display.contains("invalid"));
266 }
267
268 #[test]
269 fn test_error_source_chain_llm() {
270 let llm_err = crate::llm::LLMError::RateLimited;
271 let err = RavenClawsError::Llm(llm_err);
272 let display = format!("{}", err);
273 assert!(display.contains("LLM error"));
274 assert!(display.contains("Rate limit exceeded"));
275 }
276
277 #[test]
278 fn test_error_clone_not_required() {
279 fn _check_no_clone<T>() {
282 }
284 _check_no_clone::<RavenClawsError>();
285 }
286}