1use thiserror::Error;
7
8#[derive(Error, Debug)]
16pub enum RragError {
17 #[error("Document processing failed: {message}")]
19 DocumentProcessing {
20 message: String,
22 #[source]
23 source: Option<Box<dyn std::error::Error + Send + Sync>>,
25 },
26
27 #[error("Embedding generation failed for {content_type}: {message}")]
29 Embedding {
30 content_type: String,
32 message: String,
34 #[source]
35 source: Option<Box<dyn std::error::Error + Send + Sync>>,
37 },
38
39 #[error("Vector storage operation failed: {operation}")]
41 Storage {
42 operation: String,
44 #[source]
45 source: Box<dyn std::error::Error + Send + Sync>,
47 },
48
49 #[error("rsllm client error: {operation}")]
51 RsllmClient {
52 operation: String,
54 #[source]
55 source: Box<dyn std::error::Error + Send + Sync>,
57 },
58
59 #[error("Retrieval failed: {query}")]
61 Retrieval {
62 query: String,
64 #[source]
65 source: Option<Box<dyn std::error::Error + Send + Sync>>,
67 },
68
69 #[error("Tool '{tool}' execution failed: {message}")]
71 ToolExecution {
72 tool: String,
74 message: String,
76 #[source]
77 source: Option<Box<dyn std::error::Error + Send + Sync>>,
79 },
80
81 #[error("Configuration error: {field}")]
83 Configuration {
84 field: String,
86 expected: String,
88 actual: String,
90 },
91
92 #[error("Network operation failed: {operation}")]
94 Network {
95 operation: String,
97 #[source]
98 source: Box<dyn std::error::Error + Send + Sync>,
100 },
101
102 #[error("Serialization error: {data_type}")]
104 Serialization {
105 data_type: String,
107 #[source]
108 source: serde_json::Error,
110 },
111
112 #[error("Operation timed out after {duration_ms}ms: {operation}")]
114 Timeout {
115 operation: String,
117 duration_ms: u64,
119 },
120
121 #[error("Memory operation failed: {operation}")]
123 Memory {
124 operation: String,
126 message: String,
128 },
129
130 #[error("Stream error in {context}: {message}")]
132 Stream {
133 context: String,
135 message: String,
137 },
138
139 #[error("Agent execution failed: {agent_id}")]
141 Agent {
142 agent_id: String,
144 message: String,
146 #[source]
147 source: Option<Box<dyn std::error::Error + Send + Sync>>,
149 },
150
151 #[error("Validation failed: {field}")]
153 Validation {
154 field: String,
156 constraint: String,
158 value: String,
160 },
161}
162
163impl RragError {
164 pub fn document_processing(message: impl Into<String>) -> Self {
166 Self::DocumentProcessing {
167 message: message.into(),
168 source: None,
169 }
170 }
171
172 pub fn document_processing_with_source(
174 message: impl Into<String>,
175 source: impl std::error::Error + Send + Sync + 'static,
176 ) -> Self {
177 Self::DocumentProcessing {
178 message: message.into(),
179 source: Some(Box::new(source)),
180 }
181 }
182
183 pub fn embedding(content_type: impl Into<String>, message: impl Into<String>) -> Self {
185 Self::Embedding {
186 content_type: content_type.into(),
187 message: message.into(),
188 source: None,
189 }
190 }
191
192 pub fn storage(
194 operation: impl Into<String>,
195 source: impl std::error::Error + Send + Sync + 'static,
196 ) -> Self {
197 Self::Storage {
198 operation: operation.into(),
199 source: Box::new(source),
200 }
201 }
202
203 pub fn rsllm_client(
205 operation: impl Into<String>,
206 source: impl std::error::Error + Send + Sync + 'static,
207 ) -> Self {
208 Self::RsllmClient {
209 operation: operation.into(),
210 source: Box::new(source),
211 }
212 }
213
214 pub fn retrieval(query: impl Into<String>) -> Self {
216 Self::Retrieval {
217 query: query.into(),
218 source: None,
219 }
220 }
221
222 pub fn evaluation(message: impl Into<String>) -> Self {
224 Self::Agent {
225 agent_id: "evaluation".to_string(),
226 message: message.into(),
227 source: None,
228 }
229 }
230
231 pub fn tool_execution(tool: impl Into<String>, message: impl Into<String>) -> Self {
233 Self::ToolExecution {
234 tool: tool.into(),
235 message: message.into(),
236 source: None,
237 }
238 }
239
240 pub fn config(
242 field: impl Into<String>,
243 expected: impl Into<String>,
244 actual: impl Into<String>,
245 ) -> Self {
246 Self::Configuration {
247 field: field.into(),
248 expected: expected.into(),
249 actual: actual.into(),
250 }
251 }
252
253 pub fn timeout(operation: impl Into<String>, duration_ms: u64) -> Self {
255 Self::Timeout {
256 operation: operation.into(),
257 duration_ms,
258 }
259 }
260
261 pub fn memory(operation: impl Into<String>, message: impl Into<String>) -> Self {
263 Self::Memory {
264 operation: operation.into(),
265 message: message.into(),
266 }
267 }
268
269 pub fn stream(context: impl Into<String>, message: impl Into<String>) -> Self {
271 Self::Stream {
272 context: context.into(),
273 message: message.into(),
274 }
275 }
276
277 pub fn agent(agent_id: impl Into<String>, message: impl Into<String>) -> Self {
279 Self::Agent {
280 agent_id: agent_id.into(),
281 message: message.into(),
282 source: None,
283 }
284 }
285
286 pub fn validation(
288 field: impl Into<String>,
289 constraint: impl Into<String>,
290 value: impl Into<String>,
291 ) -> Self {
292 Self::Validation {
293 field: field.into(),
294 constraint: constraint.into(),
295 value: value.into(),
296 }
297 }
298
299 pub fn network(
301 operation: impl Into<String>,
302 source: impl std::error::Error + Send + Sync + 'static,
303 ) -> Self {
304 Self::Network {
305 operation: operation.into(),
306 source: Box::new(source),
307 }
308 }
309
310 pub fn configuration(message: impl Into<String>) -> Self {
312 Self::Configuration {
313 field: "configuration".to_string(),
314 expected: "valid configuration".to_string(),
315 actual: message.into(),
316 }
317 }
318
319 pub fn serialization_with_message(
321 data_type: impl Into<String>,
322 message: impl Into<String>,
323 ) -> Self {
324 Self::Agent {
325 agent_id: "serialization".to_string(),
326 message: format!("{}: {}", data_type.into(), message.into()),
327 source: None,
328 }
329 }
330
331 pub fn io_error(message: impl Into<String>) -> Self {
333 Self::Network {
334 operation: "io_operation".to_string(),
335 source: Box::new(std::io::Error::new(
336 std::io::ErrorKind::Other,
337 message.into(),
338 )),
339 }
340 }
341
342 pub fn is_retryable(&self) -> bool {
344 matches!(
345 self,
346 Self::Network { .. }
347 | Self::Timeout { .. }
348 | Self::RsllmClient { .. }
349 | Self::Stream { .. }
350 )
351 }
352
353 pub fn category(&self) -> &'static str {
355 match self {
356 Self::DocumentProcessing { .. } => "document_processing",
357 Self::Embedding { .. } => "embedding",
358 Self::Storage { .. } => "storage",
359 Self::RsllmClient { .. } => "rsllm_client",
360 Self::Retrieval { .. } => "retrieval",
361 Self::ToolExecution { .. } => "tool_execution",
362 Self::Configuration { .. } => "configuration",
363 Self::Network { .. } => "network",
364 Self::Serialization { .. } => "serialization",
365 Self::Timeout { .. } => "timeout",
366 Self::Memory { .. } => "memory",
367 Self::Stream { .. } => "stream",
368 Self::Agent { agent_id, .. } => {
369 if agent_id == "evaluation" {
370 "evaluation"
371 } else {
372 "agent"
373 }
374 }
375 Self::Validation { .. } => "validation",
376 }
377 }
378
379 pub fn severity(&self) -> ErrorSeverity {
381 match self {
382 Self::Configuration { .. } | Self::Validation { .. } => ErrorSeverity::Critical,
383 Self::Storage { .. } | Self::RsllmClient { .. } => ErrorSeverity::High,
384 Self::DocumentProcessing { .. } | Self::Embedding { .. } | Self::Retrieval { .. } => {
385 ErrorSeverity::Medium
386 }
387 Self::ToolExecution { .. } | Self::Agent { .. } => ErrorSeverity::Medium,
388 Self::Network { .. } | Self::Timeout { .. } | Self::Stream { .. } => ErrorSeverity::Low,
389 Self::Serialization { .. } | Self::Memory { .. } => ErrorSeverity::Low,
390 }
391 }
392}
393
394#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
396pub enum ErrorSeverity {
397 Low = 1,
399 Medium = 2,
401 High = 3,
403 Critical = 4,
405}
406
407impl std::fmt::Display for ErrorSeverity {
408 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409 match self {
410 Self::Low => write!(f, "LOW"),
411 Self::Medium => write!(f, "MEDIUM"),
412 Self::High => write!(f, "HIGH"),
413 Self::Critical => write!(f, "CRITICAL"),
414 }
415 }
416}
417
418pub type RragResult<T> = std::result::Result<T, RragError>;
420
421pub trait RragResultExt<T> {
423 fn with_rrag_context(self, context: &str) -> RragResult<T>;
425
426 fn map_to_rrag_error<F>(self, f: F) -> RragResult<T>
428 where
429 F: FnOnce() -> RragError;
430}
431
432impl<T, E> RragResultExt<T> for std::result::Result<T, E>
433where
434 E: std::error::Error + Send + Sync + 'static,
435{
436 fn with_rrag_context(self, context: &str) -> RragResult<T> {
437 self.map_err(|e| RragError::Agent {
438 agent_id: context.to_string(),
439 message: e.to_string(),
440 source: Some(Box::new(e)),
441 })
442 }
443
444 fn map_to_rrag_error<F>(self, f: F) -> RragResult<T>
445 where
446 F: FnOnce() -> RragError,
447 {
448 self.map_err(|_| f())
449 }
450}
451
452impl From<serde_json::Error> for RragError {
454 fn from(err: serde_json::Error) -> Self {
455 Self::Serialization {
456 data_type: "json".to_string(),
457 source: err,
458 }
459 }
460}
461
462#[cfg(feature = "http")]
463impl From<reqwest::Error> for RragError {
464 fn from(err: reqwest::Error) -> Self {
465 Self::Network {
466 operation: "http_request".to_string(),
467 source: Box::new(err),
468 }
469 }
470}
471
472impl From<tokio::time::error::Elapsed> for RragError {
473 fn from(_err: tokio::time::error::Elapsed) -> Self {
474 Self::Timeout {
475 operation: "async_operation".to_string(),
476 duration_ms: 0, }
478 }
479}
480
481#[cfg(test)]
482mod tests {
483 use super::*;
484
485 #[test]
486 fn test_error_categories() {
487 assert_eq!(
488 RragError::document_processing("test").category(),
489 "document_processing"
490 );
491 assert_eq!(RragError::timeout("op", 1000).category(), "timeout");
492 assert_eq!(
493 RragError::config("field", "expected", "actual").category(),
494 "configuration"
495 );
496 }
497
498 #[test]
499 fn test_error_severity() {
500 assert_eq!(
501 RragError::config("field", "expected", "actual").severity(),
502 ErrorSeverity::Critical
503 );
504 assert_eq!(
505 RragError::timeout("op", 1000).severity(),
506 ErrorSeverity::Low
507 );
508 assert_eq!(
509 RragError::storage("op", std::io::Error::new(std::io::ErrorKind::Other, "test"))
510 .severity(),
511 ErrorSeverity::High
512 );
513 }
514
515 #[test]
516 fn test_retryable() {
517 assert!(RragError::timeout("op", 1000).is_retryable());
518 assert!(!RragError::config("field", "expected", "actual").is_retryable());
519 }
520
521 #[test]
522 fn test_error_construction() {
523 let err = RragError::tool_execution("calculator", "invalid input");
524 if let RragError::ToolExecution { tool, message, .. } = err {
525 assert_eq!(tool, "calculator");
526 assert_eq!(message, "invalid input");
527 } else {
528 panic!("Wrong error type");
529 }
530 }
531}
532
533#[cfg(feature = "rexis-llm-client")]
535impl From<rexis_llm::RsllmError> for RragError {
536 fn from(err: rexis_llm::RsllmError) -> Self {
537 RragError::RsllmClient {
538 operation: "LLM operation".to_string(),
539 source: Box::new(err),
540 }
541 }
542}