llm_agent_runtime/
error.rs1#[non_exhaustive]
18#[derive(Debug, thiserror::Error)]
19pub enum AgentRuntimeError {
20 #[error("Memory operation failed: {0}")]
22 Memory(String),
23
24 #[error("Graph operation failed: {0}")]
26 Graph(String),
27
28 #[error("Orchestration failed: {0}")]
30 Orchestration(String),
31
32 #[error("Agent loop error: {0}")]
34 AgentLoop(String),
35
36 #[error("Runtime not configured: missing '{0}'")]
38 NotConfigured(&'static str),
39
40 #[error("Circuit breaker open for '{service}'")]
42 CircuitOpen {
43 service: String,
45 },
46
47 #[error("Backpressure threshold exceeded: queue depth {depth}/{capacity}")]
49 BackpressureShed {
50 depth: usize,
52 capacity: usize,
54 },
55
56 #[error("Deduplication key collision: {key}")]
58 DeduplicationConflict {
59 key: String,
61 },
62
63 #[error("Provider error: {0}")]
65 Provider(String),
66
67 #[error("Persistence error: {0}")]
69 Persistence(String),
70
71 #[error("Validation failed for field '{field}': [{code}] {message}")]
73 Validation {
74 field: String,
76 code: String,
78 message: String,
80 },
81}
82
83impl AgentRuntimeError {
84 pub fn is_circuit_open(&self) -> bool {
86 matches!(self, Self::CircuitOpen { .. })
87 }
88
89 pub fn is_backpressure(&self) -> bool {
91 matches!(self, Self::BackpressureShed { .. })
92 }
93
94 pub fn is_provider(&self) -> bool {
96 matches!(self, Self::Provider(_))
97 }
98
99 pub fn is_validation(&self) -> bool {
101 matches!(self, Self::Validation { .. })
102 }
103
104 pub fn is_memory(&self) -> bool {
106 matches!(self, Self::Memory(_))
107 }
108
109 pub fn is_graph(&self) -> bool {
111 matches!(self, Self::Graph(_))
112 }
113
114 pub fn is_agent_loop(&self) -> bool {
116 matches!(self, Self::AgentLoop(_))
117 }
118
119 pub fn is_orchestration(&self) -> bool {
121 matches!(self, Self::Orchestration(_))
122 }
123
124 pub fn is_persistence(&self) -> bool {
126 matches!(self, Self::Persistence(_))
127 }
128
129 pub fn is_not_configured(&self) -> bool {
131 matches!(self, Self::NotConfigured(_))
132 }
133
134 pub fn is_deduplication_conflict(&self) -> bool {
136 matches!(self, Self::DeduplicationConflict { .. })
137 }
138
139 pub fn is_retryable(&self) -> bool {
146 matches!(self, Self::Provider(_) | Self::Persistence(_))
147 }
148}
149
150impl From<serde_json::Error> for AgentRuntimeError {
151 fn from(e: serde_json::Error) -> Self {
152 AgentRuntimeError::AgentLoop(format!("JSON error: {e}"))
153 }
154}
155
156impl From<std::io::Error> for AgentRuntimeError {
157 fn from(e: std::io::Error) -> Self {
158 AgentRuntimeError::Persistence(format!("I/O error: {e}"))
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_memory_error_display() {
168 let e = AgentRuntimeError::Memory("store full".into());
169 assert_eq!(e.to_string(), "Memory operation failed: store full");
170 }
171
172 #[test]
173 fn test_graph_error_display() {
174 let e = AgentRuntimeError::Graph("entity not found".into());
175 assert_eq!(e.to_string(), "Graph operation failed: entity not found");
176 }
177
178 #[test]
179 fn test_orchestration_error_display() {
180 let e = AgentRuntimeError::Orchestration("pipeline stalled".into());
181 assert_eq!(e.to_string(), "Orchestration failed: pipeline stalled");
182 }
183
184 #[test]
185 fn test_agent_loop_error_display() {
186 let e = AgentRuntimeError::AgentLoop("max iterations".into());
187 assert_eq!(e.to_string(), "Agent loop error: max iterations");
188 }
189
190 #[test]
191 fn test_not_configured_error_display() {
192 let e = AgentRuntimeError::NotConfigured("memory");
193 assert_eq!(e.to_string(), "Runtime not configured: missing 'memory'");
194 }
195
196 #[test]
197 fn test_circuit_open_error_display() {
198 let e = AgentRuntimeError::CircuitOpen {
199 service: "llm-api".into(),
200 };
201 assert_eq!(e.to_string(), "Circuit breaker open for 'llm-api'");
202 }
203
204 #[test]
205 fn test_backpressure_shed_error_display() {
206 let e = AgentRuntimeError::BackpressureShed {
207 depth: 100,
208 capacity: 100,
209 };
210 assert_eq!(
211 e.to_string(),
212 "Backpressure threshold exceeded: queue depth 100/100"
213 );
214 }
215
216 #[test]
217 fn test_deduplication_conflict_display() {
218 let e = AgentRuntimeError::DeduplicationConflict {
219 key: "abc123".into(),
220 };
221 assert_eq!(e.to_string(), "Deduplication key collision: abc123");
222 }
223
224 #[test]
225 fn test_error_is_send_sync() {
226 fn assert_send_sync<T: Send + Sync>() {}
227 assert_send_sync::<AgentRuntimeError>();
228 }
229
230 #[test]
231 fn test_error_debug_format() {
232 let e = AgentRuntimeError::Memory("test".into());
233 let debug = format!("{:?}", e);
234 assert!(debug.contains("Memory"));
235 }
236
237 #[test]
238 fn test_validation_error_display() {
239 let e = AgentRuntimeError::Validation {
240 field: "n".into(),
241 code: "out_of_range".into(),
242 message: "n must be between 1 and 100".into(),
243 };
244 assert_eq!(
245 e.to_string(),
246 "Validation failed for field 'n': [out_of_range] n must be between 1 and 100"
247 );
248 }
249
250 #[test]
251 fn test_is_circuit_open_true() {
252 let e = AgentRuntimeError::CircuitOpen { service: "svc".into() };
253 assert!(e.is_circuit_open());
254 assert!(!e.is_backpressure());
255 assert!(!e.is_provider());
256 assert!(!e.is_validation());
257 assert!(!e.is_memory());
258 assert!(!e.is_graph());
259 }
260
261 #[test]
262 fn test_is_backpressure_true() {
263 let e = AgentRuntimeError::BackpressureShed { depth: 5, capacity: 5 };
264 assert!(e.is_backpressure());
265 assert!(!e.is_circuit_open());
266 }
267
268 #[test]
269 fn test_is_provider_true() {
270 let e = AgentRuntimeError::Provider("timeout".into());
271 assert!(e.is_provider());
272 assert!(!e.is_memory());
273 }
274
275 #[test]
276 fn test_is_validation_true() {
277 let e = AgentRuntimeError::Validation {
278 field: "x".into(),
279 code: "bad".into(),
280 message: "msg".into(),
281 };
282 assert!(e.is_validation());
283 assert!(!e.is_graph());
284 }
285
286 #[test]
287 fn test_is_memory_true() {
288 let e = AgentRuntimeError::Memory("oom".into());
289 assert!(e.is_memory());
290 assert!(!e.is_validation());
291 }
292
293 #[test]
294 fn test_is_graph_true() {
295 let e = AgentRuntimeError::Graph("no such entity".into());
296 assert!(e.is_graph());
297 assert!(!e.is_memory());
298 }
299
300 #[test]
303 fn test_is_persistence_true() {
304 let e = AgentRuntimeError::Persistence("disk full".into());
305 assert!(e.is_persistence());
306 assert!(!e.is_memory());
307 }
308
309 #[test]
310 fn test_is_not_configured_true() {
311 let e = AgentRuntimeError::NotConfigured("graph");
312 assert!(e.is_not_configured());
313 assert!(!e.is_persistence());
314 }
315
316 #[test]
317 fn test_is_deduplication_conflict_true() {
318 let e = AgentRuntimeError::DeduplicationConflict { key: "req-1".into() };
319 assert!(e.is_deduplication_conflict());
320 assert!(!e.is_circuit_open());
321 }
322
323 #[test]
324 fn test_is_retryable_true_for_provider() {
325 let e = AgentRuntimeError::Provider("503".into());
326 assert!(e.is_retryable());
327 }
328
329 #[test]
330 fn test_is_retryable_true_for_persistence() {
331 let e = AgentRuntimeError::Persistence("io error".into());
332 assert!(e.is_retryable());
333 }
334
335 #[test]
336 fn test_is_retryable_false_for_logic_errors() {
337 assert!(!AgentRuntimeError::Memory("x".into()).is_retryable());
338 assert!(!AgentRuntimeError::Graph("x".into()).is_retryable());
339 assert!(!AgentRuntimeError::Orchestration("x".into()).is_retryable());
340 assert!(!AgentRuntimeError::CircuitOpen { service: "s".into() }.is_retryable());
341 }
342
343 #[test]
344 fn test_from_serde_json_error() {
345 let json_err = serde_json::from_str::<serde_json::Value>("{invalid}").unwrap_err();
346 let e = AgentRuntimeError::from(json_err);
347 assert!(matches!(e, AgentRuntimeError::AgentLoop(_)));
348 }
349
350 #[test]
351 fn test_provider_error_display() {
352 let e = AgentRuntimeError::Provider("rate limited".into());
353 assert!(e.to_string().contains("rate limited"));
354 }
355
356 #[test]
357 fn test_persistence_error_display() {
358 let e = AgentRuntimeError::Persistence("file not found".into());
359 assert!(e.to_string().contains("file not found"));
360 }
361
362 #[test]
365 fn test_is_agent_loop_true_for_agent_loop_variant() {
366 let e = AgentRuntimeError::AgentLoop("step failed".into());
367 assert!(e.is_agent_loop());
368 }
369
370 #[test]
371 fn test_is_agent_loop_false_for_other_variants() {
372 let e = AgentRuntimeError::Memory("oom".into());
373 assert!(!e.is_agent_loop());
374 }
375
376 #[test]
377 fn test_is_orchestration_true_for_orchestration_variant() {
378 let e = AgentRuntimeError::Orchestration("pipeline stalled".into());
379 assert!(e.is_orchestration());
380 }
381
382 #[test]
383 fn test_is_orchestration_false_for_other_variants() {
384 let e = AgentRuntimeError::Graph("cycle".into());
385 assert!(!e.is_orchestration());
386 }
387}