rexis_llm/
error.rs

1//! # RSLLM Error Handling
2//!
3//! Comprehensive error types for the RSLLM client library.
4//! Designed for precise error categorization and helpful debugging.
5
6use thiserror::Error;
7
8/// Result type for RSLLM operations
9pub type RsllmResult<T> = Result<T, RsllmError>;
10
11/// Comprehensive error types for RSLLM operations
12#[derive(Error, Debug)]
13pub enum RsllmError {
14    /// Configuration errors
15    #[error("Configuration error: {message}")]
16    Configuration {
17        message: String,
18        #[source]
19        source: Option<Box<dyn std::error::Error + Send + Sync>>,
20    },
21
22    /// Provider-specific errors
23    #[error("Provider error ({provider}): {message}")]
24    Provider {
25        provider: String,
26        message: String,
27        #[source]
28        source: Option<Box<dyn std::error::Error + Send + Sync>>,
29    },
30
31    /// HTTP/Network errors
32    #[error("Network error: {message}")]
33    Network {
34        message: String,
35        status_code: Option<u16>,
36        #[source]
37        source: Option<Box<dyn std::error::Error + Send + Sync>>,
38    },
39
40    /// Authentication errors
41    #[error("Authentication error: {message}")]
42    Authentication { message: String },
43
44    /// Rate limiting errors
45    #[error("Rate limit exceeded: {message}")]
46    RateLimit {
47        message: String,
48        retry_after: Option<std::time::Duration>,
49    },
50
51    /// API errors from providers
52    #[error("API error ({provider}): {message} (code: {code})")]
53    Api {
54        provider: String,
55        message: String,
56        code: String,
57        #[source]
58        source: Option<Box<dyn std::error::Error + Send + Sync>>,
59    },
60
61    /// Serialization/Deserialization errors
62    #[error("Serialization error: {message}")]
63    Serialization {
64        message: String,
65        #[source]
66        source: Option<Box<dyn std::error::Error + Send + Sync>>,
67    },
68
69    /// Streaming errors
70    #[error("Streaming error: {message}")]
71    Streaming {
72        message: String,
73        #[source]
74        source: Option<Box<dyn std::error::Error + Send + Sync>>,
75    },
76
77    /// Timeout errors
78    #[error("Operation timed out after {timeout_ms}ms: {operation}")]
79    Timeout { operation: String, timeout_ms: u64 },
80
81    /// Validation errors
82    #[error("Validation error: {field} - {message}")]
83    Validation { field: String, message: String },
84
85    /// Resource not found errors
86    #[error("Resource not found: {resource}")]
87    NotFound { resource: String },
88
89    /// Invalid state errors
90    #[error("Invalid state: {message}")]
91    InvalidState { message: String },
92
93    /// Tool-related errors
94    #[error("Tool error: {message}")]
95    Tool {
96        message: String,
97        #[source]
98        source: Option<Box<dyn std::error::Error + Send + Sync>>,
99    },
100}
101
102impl RsllmError {
103    /// Create a configuration error
104    pub fn configuration(message: impl Into<String>) -> Self {
105        Self::Configuration {
106            message: message.into(),
107            source: None,
108        }
109    }
110
111    /// Create a configuration error with source
112    pub fn configuration_with_source(
113        message: impl Into<String>,
114        source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
115    ) -> Self {
116        Self::Configuration {
117            message: message.into(),
118            source: Some(source.into()),
119        }
120    }
121
122    /// Create a provider error
123    pub fn provider(provider: impl Into<String>, message: impl Into<String>) -> Self {
124        Self::Provider {
125            provider: provider.into(),
126            message: message.into(),
127            source: None,
128        }
129    }
130
131    /// Create a provider error with source
132    pub fn provider_with_source(
133        provider: impl Into<String>,
134        message: impl Into<String>,
135        source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
136    ) -> Self {
137        Self::Provider {
138            provider: provider.into(),
139            message: message.into(),
140            source: Some(source.into()),
141        }
142    }
143
144    /// Create a network error
145    pub fn network(message: impl Into<String>) -> Self {
146        Self::Network {
147            message: message.into(),
148            status_code: None,
149            source: None,
150        }
151    }
152
153    /// Create a network error with status code
154    pub fn network_with_status(message: impl Into<String>, status_code: u16) -> Self {
155        Self::Network {
156            message: message.into(),
157            status_code: Some(status_code),
158            source: None,
159        }
160    }
161
162    /// Create an authentication error
163    pub fn authentication(message: impl Into<String>) -> Self {
164        Self::Authentication {
165            message: message.into(),
166        }
167    }
168
169    /// Create a rate limit error
170    pub fn rate_limit(
171        message: impl Into<String>,
172        retry_after: Option<std::time::Duration>,
173    ) -> Self {
174        Self::RateLimit {
175            message: message.into(),
176            retry_after,
177        }
178    }
179
180    /// Create an API error
181    pub fn api(
182        provider: impl Into<String>,
183        message: impl Into<String>,
184        code: impl Into<String>,
185    ) -> Self {
186        Self::Api {
187            provider: provider.into(),
188            message: message.into(),
189            code: code.into(),
190            source: None,
191        }
192    }
193
194    /// Create a serialization error
195    pub fn serialization(message: impl Into<String>) -> Self {
196        Self::Serialization {
197            message: message.into(),
198            source: None,
199        }
200    }
201
202    /// Create a streaming error
203    pub fn streaming(message: impl Into<String>) -> Self {
204        Self::Streaming {
205            message: message.into(),
206            source: None,
207        }
208    }
209
210    /// Create a timeout error
211    pub fn timeout(operation: impl Into<String>, timeout_ms: u64) -> Self {
212        Self::Timeout {
213            operation: operation.into(),
214            timeout_ms,
215        }
216    }
217
218    /// Create a validation error
219    pub fn validation(field: impl Into<String>, message: impl Into<String>) -> Self {
220        Self::Validation {
221            field: field.into(),
222            message: message.into(),
223        }
224    }
225
226    /// Create a not found error
227    pub fn not_found(resource: impl Into<String>) -> Self {
228        Self::NotFound {
229            resource: resource.into(),
230        }
231    }
232
233    /// Create an invalid state error
234    pub fn invalid_state(message: impl Into<String>) -> Self {
235        Self::InvalidState {
236            message: message.into(),
237        }
238    }
239
240    /// Get error category for metrics/logging
241    pub fn category(&self) -> &'static str {
242        match self {
243            Self::Configuration { .. } => "configuration",
244            Self::Provider { .. } => "provider",
245            Self::Network { .. } => "network",
246            Self::Authentication { .. } => "authentication",
247            Self::RateLimit { .. } => "rate_limit",
248            Self::Api { .. } => "api",
249            Self::Serialization { .. } => "serialization",
250            Self::Streaming { .. } => "streaming",
251            Self::Timeout { .. } => "timeout",
252            Self::Validation { .. } => "validation",
253            Self::NotFound { .. } => "not_found",
254            Self::InvalidState { .. } => "invalid_state",
255            Self::Tool { .. } => "tool",
256        }
257    }
258
259    /// Check if error is retryable
260    pub fn is_retryable(&self) -> bool {
261        match self {
262            Self::Network { .. } => true,
263            Self::RateLimit { .. } => true,
264            Self::Timeout { .. } => true,
265            Self::Provider { .. } => false, // Depends on specific provider error
266            Self::Api { .. } => false,      // Depends on specific API error
267            _ => false,
268        }
269    }
270
271    /// Get retry delay if applicable
272    pub fn retry_delay(&self) -> Option<std::time::Duration> {
273        match self {
274            Self::RateLimit { retry_after, .. } => *retry_after,
275            Self::Network { .. } => Some(std::time::Duration::from_secs(1)),
276            Self::Timeout { .. } => Some(std::time::Duration::from_secs(2)),
277            _ => None,
278        }
279    }
280}
281
282// Implement conversions from common error types
283impl From<serde_json::Error> for RsllmError {
284    fn from(err: serde_json::Error) -> Self {
285        Self::serialization(format!("JSON error: {}", err))
286    }
287}
288
289impl From<url::ParseError> for RsllmError {
290    fn from(err: url::ParseError) -> Self {
291        Self::configuration(format!("Invalid URL: {}", err))
292    }
293}
294
295#[cfg(feature = "openai")]
296impl From<reqwest::Error> for RsllmError {
297    fn from(err: reqwest::Error) -> Self {
298        if err.is_timeout() {
299            Self::timeout("HTTP request", 30000) // Default 30s timeout
300        } else if err.is_connect() {
301            Self::network(format!("Connection error: {}", err))
302        } else if let Some(status) = err.status() {
303            Self::network_with_status(format!("HTTP error: {}", err), status.as_u16())
304        } else {
305            Self::network(format!("Request error: {}", err))
306        }
307    }
308}
309
310impl From<tokio::time::error::Elapsed> for RsllmError {
311    fn from(_err: tokio::time::error::Elapsed) -> Self {
312        Self::timeout("operation", 0)
313    }
314}
315
316impl From<crate::tools::ToolRegistryError> for RsllmError {
317    fn from(err: crate::tools::ToolRegistryError) -> Self {
318        Self::Tool {
319            message: err.to_string(),
320            source: Some(Box::new(err)),
321        }
322    }
323}