tlq_client/
error.rs

1use thiserror::Error;
2
3/// Comprehensive error type for TLQ client operations.
4///
5/// This enum covers all possible error conditions that can occur when interacting
6/// with a TLQ server, including network issues, server errors, validation failures,
7/// and client-side problems. Errors are classified as either retryable or non-retryable
8/// using the [`is_retryable`](Self::is_retryable) method.
9///
10/// # Error Categories
11///
12/// **Retryable errors** (transient issues that may succeed on retry):
13/// - [`Connection`](Self::Connection) - Network connectivity problems
14/// - [`Timeout`](Self::Timeout) - Request timeouts
15/// - [`Io`](Self::Io) - I/O errors from the underlying transport
16///
17/// **Non-retryable errors** (permanent failures that won't succeed on retry):
18/// - [`Server`](Self::Server) - HTTP 4xx/5xx responses from the server
19/// - [`Validation`](Self::Validation) - Invalid request parameters
20/// - [`Serialization`](Self::Serialization) - JSON parsing errors
21/// - [`MaxRetriesExceeded`](Self::MaxRetriesExceeded) - Retry limit reached
22/// - [`MessageTooLarge`](Self::MessageTooLarge) - Message exceeds size limit
23///
24/// # Examples
25///
26/// ```no_run
27/// use tlq_client::{TlqClient, TlqError};
28///
29/// #[tokio::main]
30/// async fn main() {
31///     let client = TlqClient::new("localhost", 1337).unwrap();
32///     
33///     match client.add_message("test").await {
34///         Ok(message) => println!("Success: {}", message.id),
35///         Err(TlqError::MessageTooLarge { size }) => {
36///             println!("Message too large: {} bytes", size);
37///         },
38///         Err(TlqError::Connection(msg)) => {
39///             println!("Connection failed: {}", msg);
40///         },
41///         Err(e) => println!("Other error: {}", e),
42///     }
43/// }
44/// ```
45#[derive(Error, Debug)]
46pub enum TlqError {
47    /// Network connection error
48    ///
49    /// Indicates problems connecting to the TLQ server, such as connection
50    /// refused, network unreachable, or DNS resolution failures.
51    #[error("Connection error: {0}")]
52    Connection(String),
53
54    /// Request timeout error
55    ///
56    /// The operation exceeded the configured timeout period. The timeout
57    /// duration is specified in milliseconds.
58    #[error("Timeout error after {0}ms")]
59    Timeout(u64),
60
61    /// HTTP server error response
62    ///
63    /// The TLQ server returned an HTTP error status code (4xx or 5xx).
64    /// Includes both the status code and any error message from the server.
65    #[error("Server error: {status} - {message}")]
66    Server { status: u16, message: String },
67
68    /// Request validation error
69    ///
70    /// Invalid parameters were provided to a client method, such as
71    /// empty message ID arrays or zero message counts.
72    #[error("Validation error: {0}")]
73    Validation(String),
74
75    /// JSON serialization/deserialization error
76    ///
77    /// Failed to parse JSON responses from the server or serialize
78    /// request data to JSON.
79    #[error("Serialization error: {0}")]
80    Serialization(#[from] serde_json::Error),
81
82    /// I/O error from underlying transport
83    ///
84    /// Low-level I/O errors from TCP socket operations, such as
85    /// connection reset, broken pipe, or permission denied.
86    #[error("IO error: {0}")]
87    Io(#[from] std::io::Error),
88
89    /// Maximum retry attempts exceeded
90    ///
91    /// The operation was retried the maximum number of times but still failed.
92    /// The retry count is configurable via [`ConfigBuilder`](crate::ConfigBuilder).
93    #[error("Max retries exceeded ({max_retries}) for operation")]
94    MaxRetriesExceeded { max_retries: u32 },
95
96    /// Message size exceeds the 64KB limit
97    ///
98    /// TLQ enforces a maximum message size of 65,536 bytes (64KB).
99    /// Messages larger than this limit are rejected.
100    #[error("Message too large: {size} bytes (max: 65536)")]
101    MessageTooLarge { size: usize },
102}
103
104impl TlqError {
105    /// Determines if this error type is retryable.
106    ///
107    /// Returns `true` for transient errors that may succeed if retried:
108    /// - [`Connection`](Self::Connection) errors
109    /// - [`Timeout`](Self::Timeout) errors  
110    /// - [`Io`](Self::Io) errors
111    ///
112    /// Returns `false` for permanent errors that won't succeed on retry:
113    /// - [`Server`](Self::Server) errors (4xx/5xx HTTP responses)
114    /// - [`Validation`](Self::Validation) errors
115    /// - [`Serialization`](Self::Serialization) errors
116    /// - [`MaxRetriesExceeded`](Self::MaxRetriesExceeded) errors
117    /// - [`MessageTooLarge`](Self::MessageTooLarge) errors
118    ///
119    /// This method is used internally by the retry mechanism to determine
120    /// whether to attempt retrying a failed operation.
121    ///
122    /// # Examples
123    ///
124    /// ```
125    /// use tlq_client::TlqError;
126    ///
127    /// let timeout_error = TlqError::Timeout(5000);
128    /// assert!(timeout_error.is_retryable());
129    ///
130    /// let validation_error = TlqError::Validation("Invalid input".to_string());
131    /// assert!(!validation_error.is_retryable());
132    /// ```
133    pub fn is_retryable(&self) -> bool {
134        matches!(
135            self,
136            TlqError::Connection(_) | TlqError::Timeout(_) | TlqError::Io(_)
137        )
138    }
139}
140
141/// Type alias for `Result<T, TlqError>`.
142///
143/// This is a convenience alias that makes function signatures more concise
144/// throughout the TLQ client library.
145///
146/// # Examples
147///
148/// ```
149/// use tlq_client::{Result, Message};
150///
151/// fn process_message() -> Result<Message> {
152///     // Return either Ok(message) or Err(TlqError)
153///     # Ok(Message::new("test".to_string()))
154/// }
155/// ```
156pub type Result<T> = std::result::Result<T, TlqError>;
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use std::io::{Error as IoError, ErrorKind};
162
163    #[test]
164    fn test_connection_error_retryable() {
165        let error = TlqError::Connection("Connection refused".to_string());
166        assert!(error.is_retryable());
167
168        let error_msg = format!("{}", error);
169        assert_eq!(error_msg, "Connection error: Connection refused");
170    }
171
172    #[test]
173    fn test_timeout_error_retryable() {
174        let error = TlqError::Timeout(5000);
175        assert!(error.is_retryable());
176
177        let error_msg = format!("{}", error);
178        assert_eq!(error_msg, "Timeout error after 5000ms");
179    }
180
181    #[test]
182    fn test_io_error_retryable() {
183        let io_error = IoError::new(ErrorKind::ConnectionRefused, "Connection refused");
184        let error = TlqError::Io(io_error);
185        assert!(error.is_retryable());
186
187        let error_msg = format!("{}", error);
188        assert!(error_msg.contains("IO error:"));
189        assert!(error_msg.contains("Connection refused"));
190    }
191
192    #[test]
193    fn test_server_error_not_retryable() {
194        let error = TlqError::Server {
195            status: 500,
196            message: "Internal Server Error".to_string(),
197        };
198        assert!(!error.is_retryable());
199
200        let error_msg = format!("{}", error);
201        assert_eq!(error_msg, "Server error: 500 - Internal Server Error");
202    }
203
204    #[test]
205    fn test_validation_error_not_retryable() {
206        let error = TlqError::Validation("Invalid input".to_string());
207        assert!(!error.is_retryable());
208
209        let error_msg = format!("{}", error);
210        assert_eq!(error_msg, "Validation error: Invalid input");
211    }
212
213    #[test]
214    fn test_serialization_error_not_retryable() {
215        // Create a serde_json error
216        let json_error = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
217        let error = TlqError::Serialization(json_error);
218        assert!(!error.is_retryable());
219
220        let error_msg = format!("{}", error);
221        assert!(error_msg.contains("Serialization error:"));
222    }
223
224    #[test]
225    fn test_max_retries_exceeded_not_retryable() {
226        let error = TlqError::MaxRetriesExceeded { max_retries: 3 };
227        assert!(!error.is_retryable());
228
229        let error_msg = format!("{}", error);
230        assert_eq!(error_msg, "Max retries exceeded (3) for operation");
231    }
232
233    #[test]
234    fn test_message_too_large_not_retryable() {
235        let error = TlqError::MessageTooLarge { size: 70000 };
236        assert!(!error.is_retryable());
237
238        let error_msg = format!("{}", error);
239        assert_eq!(error_msg, "Message too large: 70000 bytes (max: 65536)");
240    }
241
242    #[test]
243    fn test_error_from_io_error() {
244        let io_error = IoError::new(ErrorKind::PermissionDenied, "Access denied");
245        let tlq_error: TlqError = io_error.into();
246
247        assert!(tlq_error.is_retryable()); // IO errors are retryable
248        assert!(matches!(tlq_error, TlqError::Io(_)));
249    }
250
251    #[test]
252    fn test_error_from_serde_json_error() {
253        let json_error = serde_json::from_str::<serde_json::Value>("{invalid}").unwrap_err();
254        let tlq_error: TlqError = json_error.into();
255
256        assert!(!tlq_error.is_retryable()); // Serialization errors are not retryable
257        assert!(matches!(tlq_error, TlqError::Serialization(_)));
258    }
259
260    #[test]
261    fn test_different_io_error_kinds() {
262        let error_kinds = vec![
263            ErrorKind::NotFound,
264            ErrorKind::PermissionDenied,
265            ErrorKind::ConnectionRefused,
266            ErrorKind::ConnectionReset,
267            ErrorKind::TimedOut,
268            ErrorKind::Interrupted,
269        ];
270
271        for kind in error_kinds {
272            let io_error = IoError::new(kind, format!("{:?} error", kind));
273            let tlq_error = TlqError::Io(io_error);
274
275            // All IO errors should be retryable
276            assert!(tlq_error.is_retryable());
277        }
278    }
279
280    #[test]
281    fn test_server_error_status_codes() {
282        let test_cases = vec![
283            (400, "Bad Request"),
284            (401, "Unauthorized"),
285            (403, "Forbidden"),
286            (404, "Not Found"),
287            (500, "Internal Server Error"),
288            (502, "Bad Gateway"),
289            (503, "Service Unavailable"),
290            (504, "Gateway Timeout"),
291        ];
292
293        for (status, message) in test_cases {
294            let error = TlqError::Server {
295                status,
296                message: message.to_string(),
297            };
298
299            // Server errors should not be retryable
300            assert!(!error.is_retryable());
301
302            let error_msg = format!("{}", error);
303            assert!(error_msg.contains(&status.to_string()));
304            assert!(error_msg.contains(message));
305        }
306    }
307
308    #[test]
309    fn test_error_debug_formatting() {
310        let error = TlqError::Connection("test connection error".to_string());
311        let debug_str = format!("{:?}", error);
312        assert!(debug_str.contains("Connection"));
313        assert!(debug_str.contains("test connection error"));
314    }
315
316    #[test]
317    fn test_result_type_alias() {
318        // Test that our Result type alias works correctly
319        let success: Result<String> = Ok("success".to_string());
320        assert!(success.is_ok());
321        if let Ok(value) = success {
322            assert_eq!(value, "success");
323        }
324
325        let failure: Result<String> = Err(TlqError::Validation("test error".to_string()));
326        assert!(failure.is_err());
327
328        match failure {
329            Err(TlqError::Validation(msg)) => assert_eq!(msg, "test error"),
330            _ => panic!("Expected validation error"),
331        }
332    }
333
334    #[test]
335    fn test_timeout_edge_cases() {
336        // Test various timeout values
337        let timeout_0 = TlqError::Timeout(0);
338        assert!(timeout_0.is_retryable());
339        assert_eq!(format!("{}", timeout_0), "Timeout error after 0ms");
340
341        let timeout_max = TlqError::Timeout(u64::MAX);
342        assert!(timeout_max.is_retryable());
343        assert_eq!(
344            format!("{}", timeout_max),
345            format!("Timeout error after {}ms", u64::MAX)
346        );
347    }
348
349    #[test]
350    fn test_message_size_edge_cases() {
351        // Test various message sizes
352        let size_0 = TlqError::MessageTooLarge { size: 0 };
353        assert_eq!(
354            format!("{}", size_0),
355            "Message too large: 0 bytes (max: 65536)"
356        );
357
358        let size_max = TlqError::MessageTooLarge { size: usize::MAX };
359        assert_eq!(
360            format!("{}", size_max),
361            format!("Message too large: {} bytes (max: 65536)", usize::MAX)
362        );
363
364        let size_just_over = TlqError::MessageTooLarge { size: 65537 };
365        assert_eq!(
366            format!("{}", size_just_over),
367            "Message too large: 65537 bytes (max: 65536)"
368        );
369    }
370
371    #[test]
372    fn test_empty_error_messages() {
373        let connection_error = TlqError::Connection("".to_string());
374        assert_eq!(format!("{}", connection_error), "Connection error: ");
375
376        let validation_error = TlqError::Validation("".to_string());
377        assert_eq!(format!("{}", validation_error), "Validation error: ");
378
379        let server_error = TlqError::Server {
380            status: 500,
381            message: "".to_string(),
382        };
383        assert_eq!(format!("{}", server_error), "Server error: 500 - ");
384    }
385}