rexis_rag/
error.rs

1//! # RRAG Error Types
2//!
3//! Comprehensive error handling designed for the Rust ecosystem.
4//! Focuses on providing detailed context while maintaining performance.
5
6use thiserror::Error;
7
8/// Main error type for RRAG operations
9///
10/// Designed with Rust's error handling best practices:
11/// - Uses `thiserror` for automatic trait implementations
12/// - Provides structured error data for programmatic handling
13/// - Includes source chain for debugging
14/// - Categorizes errors for metrics and logging
15#[derive(Error, Debug)]
16pub enum RragError {
17    /// Document processing errors
18    #[error("Document processing failed: {message}")]
19    DocumentProcessing {
20        /// Error message describing what went wrong
21        message: String,
22        #[source]
23        /// Optional source error that caused this failure
24        source: Option<Box<dyn std::error::Error + Send + Sync>>,
25    },
26
27    /// Embedding generation errors
28    #[error("Embedding generation failed for {content_type}: {message}")]
29    Embedding {
30        /// Type of content that failed to embed (text, image, etc.)
31        content_type: String,
32        /// Error message describing the failure
33        message: String,
34        #[source]
35        /// Optional source error that caused this failure
36        source: Option<Box<dyn std::error::Error + Send + Sync>>,
37    },
38
39    /// Vector storage errors
40    #[error("Vector storage operation failed: {operation}")]
41    Storage {
42        /// Storage operation that failed (insert, query, delete, etc.)
43        operation: String,
44        #[source]
45        /// Source error from the storage backend
46        source: Box<dyn std::error::Error + Send + Sync>,
47    },
48
49    /// rsllm client errors
50    #[error("rsllm client error: {operation}")]
51    RsllmClient {
52        /// Client operation that failed (request, auth, etc.)
53        operation: String,
54        #[source]
55        /// Source error from the rsllm client
56        source: Box<dyn std::error::Error + Send + Sync>,
57    },
58
59    /// Retrieval/search errors
60    #[error("Retrieval failed: {query}")]
61    Retrieval {
62        /// Query that failed to execute
63        query: String,
64        #[source]
65        /// Optional source error that caused this failure
66        source: Option<Box<dyn std::error::Error + Send + Sync>>,
67    },
68
69    /// Tool execution errors
70    #[error("Tool '{tool}' execution failed: {message}")]
71    ToolExecution {
72        /// Name of the tool that failed
73        tool: String,
74        /// Error message from the tool execution
75        message: String,
76        #[source]
77        /// Optional source error that caused this failure
78        source: Option<Box<dyn std::error::Error + Send + Sync>>,
79    },
80
81    /// Configuration errors
82    #[error("Configuration error: {field}")]
83    Configuration {
84        /// Configuration field that has an invalid value
85        field: String,
86        /// Expected value or format for the field
87        expected: String,
88        /// Actual value that was provided
89        actual: String,
90    },
91
92    /// Network/IO errors
93    #[error("Network operation failed: {operation}")]
94    Network {
95        /// Network operation that failed (request, response, etc.)
96        operation: String,
97        #[source]
98        /// Source error from the network operation
99        source: Box<dyn std::error::Error + Send + Sync>,
100    },
101
102    /// Serialization/deserialization errors
103    #[error("Serialization error: {data_type}")]
104    Serialization {
105        /// Type of data that failed to serialize/deserialize
106        data_type: String,
107        #[source]
108        /// Source error from the serialization library
109        source: serde_json::Error,
110    },
111
112    /// Timeout errors
113    #[error("Operation timed out after {duration_ms}ms: {operation}")]
114    Timeout {
115        /// Operation that timed out
116        operation: String,
117        /// Duration in milliseconds before timeout
118        duration_ms: u64,
119    },
120
121    /// Memory/conversation errors
122    #[error("Memory operation failed: {operation}")]
123    Memory {
124        /// Memory operation that failed
125        operation: String,
126        /// Error message describing the failure
127        message: String,
128    },
129
130    /// Streaming errors
131    #[error("Stream error in {context}: {message}")]
132    Stream {
133        /// Context where the stream error occurred
134        context: String,
135        /// Error message describing the stream failure
136        message: String,
137    },
138
139    /// Agent execution errors
140    #[error("Agent execution failed: {agent_id}")]
141    Agent {
142        /// ID of the agent that encountered the error
143        agent_id: String,
144        /// Error message from the agent
145        message: String,
146        #[source]
147        /// Optional source error that caused this failure
148        source: Option<Box<dyn std::error::Error + Send + Sync>>,
149    },
150
151    /// Validation errors
152    #[error("Validation failed: {field}")]
153    Validation {
154        /// Field that failed validation
155        field: String,
156        /// Validation constraint that was violated
157        constraint: String,
158        /// Value that failed validation
159        value: String,
160    },
161}
162
163impl RragError {
164    /// Create a document processing error
165    pub fn document_processing(message: impl Into<String>) -> Self {
166        Self::DocumentProcessing {
167            message: message.into(),
168            source: None,
169        }
170    }
171
172    /// Create a document processing error with source
173    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    /// Create an embedding error
184    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    /// Create a storage error
193    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    /// Create an rsllm client error
204    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    /// Create a retrieval error
215    pub fn retrieval(query: impl Into<String>) -> Self {
216        Self::Retrieval {
217            query: query.into(),
218            source: None,
219        }
220    }
221
222    /// Create an evaluation error
223    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    /// Create a tool execution error
232    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    /// Create a configuration error
241    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    /// Create a timeout error
254    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    /// Create a memory error
262    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    /// Create a stream error
270    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    /// Create an agent error
278    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    /// Create a validation error
287    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    /// Create a network error
300    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    /// Create a configuration error (shorthand for config method)
311    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    /// Create a serialization error with message
320    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    /// Create an I/O error
332    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    /// Check if this error suggests a retry might succeed
343    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    /// Get error category for metrics and logging
354    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    /// Get error severity level
380    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/// Error severity levels for monitoring and alerting
395#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
396pub enum ErrorSeverity {
397    /// Low priority error that doesn't affect core functionality
398    Low = 1,
399    /// Medium priority error that may cause minor issues
400    Medium = 2,
401    /// High priority error that affects core functionality
402    High = 3,
403    /// Critical error that causes system failure
404    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
418/// Convenience type alias following Rust conventions
419pub type RragResult<T> = std::result::Result<T, RragError>;
420
421/// Extension trait for adding RRAG context to any Result
422pub trait RragResultExt<T> {
423    /// Add context to an error
424    fn with_rrag_context(self, context: &str) -> RragResult<T>;
425
426    /// Map to a specific RRAG error type
427    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
452// Automatic conversions from common error types
453impl 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, // Unknown duration
477        }
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// Implement From<RsllmError> for RragError
534#[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}