contextlite_client/
error.rs

1//! Error types for the ContextLite Rust client.
2//!
3//! This module provides comprehensive error handling with detailed error messages
4//! and proper error chaining for debugging.
5
6use thiserror::Error;
7
8/// The main error type for the ContextLite client
9#[derive(Error, Debug)]
10pub enum ContextLiteError {
11    /// HTTP request errors
12    #[error("HTTP request failed: {0}")]
13    HttpError(#[from] reqwest::Error),
14    
15    /// JSON serialization/deserialization errors
16    #[error("JSON processing failed: {0}")]
17    JsonError(#[from] serde_json::Error),
18    
19    /// URL parsing errors
20    #[error("Invalid URL: {0}")]
21    UrlError(#[from] url::ParseError),
22    
23    /// Authentication errors
24    #[error("Authentication failed: {message}")]
25    AuthError { 
26        /// Error message
27        message: String 
28    },
29    
30    /// Server returned an error status
31    #[error("Server error {status}: {message}")]
32    ServerError { 
33        /// HTTP status code
34        status: u16, 
35        /// Error message
36        message: String 
37    },
38    
39    /// Document validation errors
40    #[error("Document validation failed: {message}")]
41    ValidationError { 
42        /// Error message
43        message: String 
44    },
45    
46    /// Configuration errors
47    #[error("Configuration error: {message}")]
48    ConfigError { 
49        /// Error message
50        message: String 
51    },
52    
53    /// Timeout errors
54    #[error("Request timed out after {seconds} seconds")]
55    TimeoutError { 
56        /// Timeout duration in seconds
57        seconds: u64 
58    },
59    
60    /// Connection pool errors
61    #[error("Connection pool error: {message}")]
62    ConnectionError { 
63        /// Error message
64        message: String 
65    },
66    
67    /// Invalid response format
68    #[error("Invalid response format: {message}")]
69    ResponseError { 
70        /// Error message
71        message: String 
72    },
73    
74    /// Rate limiting errors
75    #[error("Rate limit exceeded: {message}")]
76    RateLimitError { 
77        /// Error message
78        message: String 
79    },
80    
81    /// Generic client errors
82    #[error("Client error: {message}")]
83    ClientError { 
84        /// Error message
85        message: String 
86    },
87}
88
89impl ContextLiteError {
90    /// Create an authentication error
91    pub fn auth(message: impl Into<String>) -> Self {
92        Self::AuthError {
93            message: message.into(),
94        }
95    }
96    
97    /// Create a server error
98    pub fn server(status: u16, message: impl Into<String>) -> Self {
99        Self::ServerError {
100            status,
101            message: message.into(),
102        }
103    }
104    
105    /// Create a validation error
106    pub fn validation(message: impl Into<String>) -> Self {
107        Self::ValidationError {
108            message: message.into(),
109        }
110    }
111    
112    /// Create a configuration error
113    pub fn config(message: impl Into<String>) -> Self {
114        Self::ConfigError {
115            message: message.into(),
116        }
117    }
118    
119    /// Create a timeout error
120    pub fn timeout(seconds: u64) -> Self {
121        Self::TimeoutError { seconds }
122    }
123    
124    /// Create a connection error
125    pub fn connection(message: impl Into<String>) -> Self {
126        Self::ConnectionError {
127            message: message.into(),
128        }
129    }
130    
131    /// Create a response error
132    pub fn response(message: impl Into<String>) -> Self {
133        Self::ResponseError {
134            message: message.into(),
135        }
136    }
137    
138    /// Create a rate limit error
139    pub fn rate_limit(message: impl Into<String>) -> Self {
140        Self::RateLimitError {
141            message: message.into(),
142        }
143    }
144    
145    /// Create a generic client error
146    pub fn client(message: impl Into<String>) -> Self {
147        Self::ClientError {
148            message: message.into(),
149        }
150    }
151    
152    /// Check if this error is retryable
153    pub fn is_retryable(&self) -> bool {
154        match self {
155            Self::HttpError(e) => {
156                // Connection errors are retryable
157                e.is_connect() || e.is_timeout()
158            }
159            Self::ServerError { status, .. } => {
160                // 5xx errors are retryable, 4xx are not
161                *status >= 500
162            }
163            Self::TimeoutError { .. } => true,
164            Self::ConnectionError { .. } => true,
165            Self::RateLimitError { .. } => true,
166            _ => false,
167        }
168    }
169    
170    /// Check if this error is a client-side error
171    pub fn is_client_error(&self) -> bool {
172        match self {
173            Self::ValidationError { .. } |
174            Self::ConfigError { .. } |
175            Self::AuthError { .. } |
176            Self::JsonError(_) |
177            Self::UrlError(_) => true,
178            Self::ServerError { status, .. } => *status >= 400 && *status < 500,
179            _ => false,
180        }
181    }
182    
183    /// Check if this error is a server-side error
184    pub fn is_server_error(&self) -> bool {
185        match self {
186            Self::ServerError { status, .. } => *status >= 500,
187            _ => false,
188        }
189    }
190}
191
192/// Result type for ContextLite operations
193pub type Result<T> = std::result::Result<T, ContextLiteError>;
194
195/// Helper function to handle HTTP response errors
196pub async fn handle_response_error(response: reqwest::Response) -> Result<reqwest::Response> {
197    let status = response.status();
198    
199    if status.is_success() {
200        Ok(response)
201    } else if status == 401 {
202        Err(ContextLiteError::auth("Invalid or missing authentication token"))
203    } else if status == 429 {
204        let message = response.text().await
205            .unwrap_or_else(|_| "Rate limit exceeded".to_string());
206        Err(ContextLiteError::rate_limit(message))
207    } else {
208        let message = response.text().await
209            .unwrap_or_else(|_| format!("HTTP {}", status.as_u16()));
210        Err(ContextLiteError::server(status.as_u16(), message))
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217    
218    #[test]
219    fn test_error_creation() {
220        let auth_err = ContextLiteError::auth("Invalid token");
221        assert!(matches!(auth_err, ContextLiteError::AuthError { .. }));
222        assert!(auth_err.is_client_error());
223        assert!(!auth_err.is_retryable());
224        
225        let server_err = ContextLiteError::server(500, "Internal error");
226        assert!(matches!(server_err, ContextLiteError::ServerError { .. }));
227        assert!(server_err.is_server_error());
228        assert!(server_err.is_retryable());
229        
230        let timeout_err = ContextLiteError::timeout(30);
231        assert!(matches!(timeout_err, ContextLiteError::TimeoutError { .. }));
232        assert!(timeout_err.is_retryable());
233    }
234    
235    #[test]
236    fn test_error_display() {
237        let auth_err = ContextLiteError::auth("Invalid token");
238        assert_eq!(auth_err.to_string(), "Authentication failed: Invalid token");
239        
240        let server_err = ContextLiteError::server(404, "Not found");
241        assert_eq!(server_err.to_string(), "Server error 404: Not found");
242        
243        let timeout_err = ContextLiteError::timeout(60);
244        assert_eq!(timeout_err.to_string(), "Request timed out after 60 seconds");
245    }
246    
247    #[test]
248    fn test_retryable_classification() {
249        // Retryable errors
250        assert!(ContextLiteError::server(500, "error").is_retryable());
251        assert!(ContextLiteError::server(502, "error").is_retryable());
252        assert!(ContextLiteError::timeout(30).is_retryable());
253        assert!(ContextLiteError::connection("error").is_retryable());
254        assert!(ContextLiteError::rate_limit("error").is_retryable());
255        
256        // Non-retryable errors
257        assert!(!ContextLiteError::server(400, "error").is_retryable());
258        assert!(!ContextLiteError::server(404, "error").is_retryable());
259        assert!(!ContextLiteError::auth("error").is_retryable());
260        assert!(!ContextLiteError::validation("error").is_retryable());
261    }
262    
263    #[test]
264    fn test_error_classification() {
265        // Client errors
266        assert!(ContextLiteError::server(400, "error").is_client_error());
267        assert!(ContextLiteError::server(404, "error").is_client_error());
268        assert!(ContextLiteError::auth("error").is_client_error());
269        assert!(ContextLiteError::validation("error").is_client_error());
270        
271        // Server errors
272        assert!(ContextLiteError::server(500, "error").is_server_error());
273        assert!(ContextLiteError::server(502, "error").is_server_error());
274        
275        // Neither client nor server errors
276        assert!(!ContextLiteError::timeout(30).is_client_error());
277        assert!(!ContextLiteError::timeout(30).is_server_error());
278    }
279}