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 #[error("Agent failed: {0}")]
37 #[allow(dead_code)]
38 AgentFailed(String),
39
40 #[error("Self-healing error: {0}")]
41 #[allow(dead_code)]
42 HealingError(String),
43
44 #[error("Kubernetes error: {0}")]
45 #[allow(dead_code)]
46 K8s(String),
47}
48
49impl RavenClawsError {
50 #[allow(dead_code)]
52 pub fn is_transient(&self) -> bool {
53 matches!(
54 self,
55 RavenClawsError::Llm(e) if e.is_transient(),
56 )
57 }
58}
59
60pub type Result<T> = std::result::Result<T, RavenClawsError>;
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn test_llm_error_variant() {
68 let err = RavenClawsError::Llm(crate::llm::LLMError::RequestFailed("timeout".to_string()));
69 assert_eq!(format!("{}", err), "LLM error: Request failed: timeout");
70 }
71
72 #[test]
73 fn test_config_error_variant() {
74 let err = RavenClawsError::Config(crate::config::ConfigError::ValidationError(
75 "bad field".to_string(),
76 ));
77 assert_eq!(
78 format!("{}", err),
79 "Configuration error: Invalid configuration: bad field"
80 );
81 }
82
83 #[test]
84 fn test_ravenfabric_error_variant() {
85 let err = RavenClawsError::RavenFabric("connection refused".to_string());
86 assert_eq!(format!("{}", err), "RavenFabric error: connection refused");
87 }
88
89 #[test]
90 fn test_command_execution_error_variant() {
91 let err = RavenClawsError::CommandExecution("command failed".to_string());
92 assert_eq!(
93 format!("{}", err),
94 "Command execution failed: command failed"
95 );
96 }
97
98 #[test]
99 fn test_security_violation_error_variant() {
100 let err = RavenClawsError::SecurityViolation("unauthorized access".to_string());
101 assert_eq!(
102 format!("{}", err),
103 "Security violation: unauthorized access"
104 );
105 }
106
107 #[test]
108 fn test_result_type_alias() {
109 let ok: i32 = 42;
110 assert_eq!(ok, 42);
111
112 let err: Result<i32> = Err(RavenClawsError::CommandExecution("fail".to_string()));
113 assert!(err.is_err());
114 }
115
116 #[tokio::test]
117 async fn test_network_error_variant() {
118 let err = RavenClawsError::Network(
122 reqwest::Client::builder()
123 .build()
124 .unwrap()
125 .get("http://invalid.example.com")
126 .send()
127 .await
128 .unwrap_err(),
129 );
130 assert!(format!("{}", err).contains("Network error"));
131 }
132
133 #[test]
134 fn test_io_error_variant() {
135 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
136 let err = RavenClawsError::IO(io_err);
137 assert!(format!("{}", err).contains("IO error"));
138 assert!(format!("{}", err).contains("file not found"));
139 }
140
141 #[test]
142 fn test_error_is_debug() {
143 let err = RavenClawsError::CommandExecution("test".to_string());
144 let debug = format!("{:?}", err);
145 assert!(debug.contains("CommandExecution"));
146 }
147
148 #[test]
149 fn test_error_is_send() {
150 fn check_send<T: Send>() {}
151 check_send::<RavenClawsError>();
152 }
153
154 #[test]
155 fn test_error_is_sync() {
156 fn check_sync<T: Sync>() {}
157 check_sync::<RavenClawsError>();
158 }
159
160 #[test]
161 fn test_from_llm_error_conversion() {
162 let llm_err = crate::llm::LLMError::RequestFailed("timeout".to_string());
163 let err: RavenClawsError = llm_err.into();
164 assert!(format!("{}", err).contains("LLM error"));
165 assert!(format!("{}", err).contains("timeout"));
166 }
167
168 #[test]
169 fn test_from_config_error_conversion() {
170 let cfg_err = crate::config::ConfigError::ValidationError("bad config".to_string());
171 let err: RavenClawsError = cfg_err.into();
172 assert!(format!("{}", err).contains("Configuration error"));
173 assert!(format!("{}", err).contains("bad config"));
174 }
175
176 #[test]
177 fn test_from_io_error_conversion() {
178 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "permission denied");
179 let err: RavenClawsError = io_err.into();
180 assert!(format!("{}", err).contains("IO error"));
181 assert!(format!("{}", err).contains("permission denied"));
182 }
183
184 #[test]
185 fn test_error_source_chain() {
186 let inner = crate::llm::LLMError::AuthFailed;
189 let err = RavenClawsError::Llm(inner);
190 let display = format!("{}", err);
191 assert!(display.contains("Authentication failed"));
192 }
193
194 #[test]
195 fn test_ravenfabric_error_construction() {
196 let err = RavenClawsError::RavenFabric("connection timeout".to_string());
197 assert_eq!(format!("{}", err), "RavenFabric error: connection timeout");
198 }
199
200 #[test]
201 fn test_security_violation_construction() {
202 let err = RavenClawsError::SecurityViolation("invalid token".to_string());
203 assert_eq!(format!("{}", err), "Security violation: invalid token");
204 }
205
206 #[test]
207 fn test_agent_failed_variant() {
208 let err = RavenClawsError::AgentFailed("worker-1 crashed".to_string());
209 assert_eq!(format!("{}", err), "Agent failed: worker-1 crashed");
210 }
211
212 #[test]
213 fn test_healing_error_variant() {
214 let err = RavenClawsError::HealingError("circuit breaker open".to_string());
215 assert_eq!(
216 format!("{}", err),
217 "Self-healing error: circuit breaker open"
218 );
219 }
220
221 #[test]
222 fn test_is_transient_llm_request_failed() {
223 let llm_err = crate::llm::LLMError::RequestFailed("timeout".to_string());
224 assert!(llm_err.is_transient());
225 }
226
227 #[test]
228 fn test_is_transient_llm_rate_limited() {
229 let llm_err = crate::llm::LLMError::RateLimited;
230 assert!(llm_err.is_transient());
231 }
232
233 #[test]
234 fn test_is_transient_llm_circuit_breaker() {
235 let llm_err = crate::llm::LLMError::CircuitBreakerOpen("openai".to_string());
236 assert!(llm_err.is_transient());
237 }
238
239 #[test]
240 fn test_is_not_transient_llm_auth_failed() {
241 let llm_err = crate::llm::LLMError::AuthFailed;
242 assert!(!llm_err.is_transient());
243 }
244
245 #[test]
246 fn test_is_not_transient_llm_invalid_response() {
247 let llm_err = crate::llm::LLMError::InvalidResponse("bad json".to_string());
248 assert!(!llm_err.is_transient());
249 }
250
251 #[test]
252 fn test_is_not_transient_llm_token_budget() {
253 let llm_err = crate::llm::LLMError::TokenBudgetExceeded;
254 assert!(!llm_err.is_transient());
255 }
256
257 #[test]
258 fn test_is_not_transient_llm_all_providers_failed() {
259 let llm_err = crate::llm::LLMError::AllProvidersFailed;
260 assert!(!llm_err.is_transient());
261 }
262
263 #[test]
264 fn test_ravenclaws_error_is_transient_via_llm() {
265 let llm_err = crate::llm::LLMError::RequestFailed("timeout".to_string());
266 let err = RavenClawsError::Llm(llm_err);
267 assert!(err.is_transient());
268 }
269
270 #[test]
271 fn test_ravenclaws_error_is_not_transient_for_non_llm() {
272 let err = RavenClawsError::CommandExecution("fail".to_string());
273 assert!(!err.is_transient());
274 }
275
276 #[test]
277 fn test_ravenclaws_error_is_not_transient_for_agent_failed() {
278 let err = RavenClawsError::AgentFailed("crashed".to_string());
279 assert!(!err.is_transient());
280 }
281
282 #[test]
283 #[allow(clippy::unnecessary_literal_unwrap)]
284 fn test_result_type_alias_ok() {
285 let result: Result<i32> = Ok(42);
286 assert!(result.is_ok());
287 assert_eq!(result.unwrap(), 42);
288 }
289
290 #[test]
291 #[allow(clippy::unnecessary_literal_unwrap)]
292 fn test_result_type_alias_err() {
293 let result: Result<i32> = Err(RavenClawsError::CommandExecution("fail".to_string()));
294 assert!(result.is_err());
295 assert_eq!(
296 format!("{}", result.unwrap_err()),
297 "Command execution failed: fail"
298 );
299 }
300
301 #[test]
302 fn test_error_into_boxed() {
303 let err = RavenClawsError::CommandExecution("boxed".to_string());
305 let boxed: Box<dyn std::error::Error> = Box::new(err);
306 assert!(format!("{}", boxed).contains("Command execution failed"));
307 }
308
309 #[test]
310 fn test_error_into_string() {
311 let err = RavenClawsError::SecurityViolation("access denied".to_string());
312 let msg: String = err.to_string();
313 assert_eq!(msg, "Security violation: access denied");
314 }
315
316 #[test]
317 fn test_error_from_reqwest() {
318 fn _check_from()
322 where
323 reqwest::Error: Into<RavenClawsError>,
324 {
325 }
326 }
328
329 #[test]
330 fn test_error_display_network_variant() {
331 let rt = tokio::runtime::Runtime::new().unwrap();
333 let err = rt.block_on(async {
334 reqwest::Client::builder()
335 .build()
336 .unwrap()
337 .get("http://invalid.example.com")
338 .send()
339 .await
340 .unwrap_err()
341 });
342 let raven_err = RavenClawsError::Network(err);
343 let display = format!("{}", raven_err);
344 assert!(display.contains("Network error"));
345 assert!(!display.is_empty());
346 }
347
348 #[test]
349 fn test_error_source_chain_io() {
350 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
352 let err = RavenClawsError::IO(io_err);
353 let display = format!("{}", err);
354 assert!(display.contains("IO error"));
355 assert!(display.contains("file not found"));
356 }
357
358 #[test]
359 fn test_error_source_chain_config() {
360 let cfg_err = crate::config::ConfigError::ValidationError("invalid".to_string());
361 let err = RavenClawsError::Config(cfg_err);
362 let display = format!("{}", err);
363 assert!(display.contains("Configuration error"));
364 assert!(display.contains("invalid"));
365 }
366
367 #[test]
368 fn test_error_source_chain_llm() {
369 let llm_err = crate::llm::LLMError::RateLimited;
370 let err = RavenClawsError::Llm(llm_err);
371 let display = format!("{}", err);
372 assert!(display.contains("LLM error"));
373 assert!(display.contains("Rate limit exceeded"));
374 }
375
376 #[test]
377 fn test_error_clone_not_required() {
378 fn _check_no_clone<T>() {
381 }
383 _check_no_clone::<RavenClawsError>();
384 }
385}