tlq_client/
error.rs

1use thiserror::Error;
2
3#[derive(Error, Debug)]
4pub enum TlqError {
5    #[error("Connection error: {0}")]
6    Connection(String),
7
8    #[error("Timeout error after {0}ms")]
9    Timeout(u64),
10
11    #[error("Server error: {status} - {message}")]
12    Server { status: u16, message: String },
13
14    #[error("Validation error: {0}")]
15    Validation(String),
16
17    #[error("Serialization error: {0}")]
18    Serialization(#[from] serde_json::Error),
19
20    #[error("IO error: {0}")]
21    Io(#[from] std::io::Error),
22
23    #[error("Max retries exceeded ({max_retries}) for operation")]
24    MaxRetriesExceeded { max_retries: u32 },
25
26    #[error("Message too large: {size} bytes (max: 65536)")]
27    MessageTooLarge { size: usize },
28}
29
30impl TlqError {
31    pub fn is_retryable(&self) -> bool {
32        matches!(
33            self,
34            TlqError::Connection(_) | TlqError::Timeout(_) | TlqError::Io(_)
35        )
36    }
37}
38
39pub type Result<T> = std::result::Result<T, TlqError>;
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44    use std::io::{Error as IoError, ErrorKind};
45
46    #[test]
47    fn test_connection_error_retryable() {
48        let error = TlqError::Connection("Connection refused".to_string());
49        assert!(error.is_retryable());
50
51        let error_msg = format!("{}", error);
52        assert_eq!(error_msg, "Connection error: Connection refused");
53    }
54
55    #[test]
56    fn test_timeout_error_retryable() {
57        let error = TlqError::Timeout(5000);
58        assert!(error.is_retryable());
59
60        let error_msg = format!("{}", error);
61        assert_eq!(error_msg, "Timeout error after 5000ms");
62    }
63
64    #[test]
65    fn test_io_error_retryable() {
66        let io_error = IoError::new(ErrorKind::ConnectionRefused, "Connection refused");
67        let error = TlqError::Io(io_error);
68        assert!(error.is_retryable());
69
70        let error_msg = format!("{}", error);
71        assert!(error_msg.contains("IO error:"));
72        assert!(error_msg.contains("Connection refused"));
73    }
74
75    #[test]
76    fn test_server_error_not_retryable() {
77        let error = TlqError::Server {
78            status: 500,
79            message: "Internal Server Error".to_string(),
80        };
81        assert!(!error.is_retryable());
82
83        let error_msg = format!("{}", error);
84        assert_eq!(error_msg, "Server error: 500 - Internal Server Error");
85    }
86
87    #[test]
88    fn test_validation_error_not_retryable() {
89        let error = TlqError::Validation("Invalid input".to_string());
90        assert!(!error.is_retryable());
91
92        let error_msg = format!("{}", error);
93        assert_eq!(error_msg, "Validation error: Invalid input");
94    }
95
96    #[test]
97    fn test_serialization_error_not_retryable() {
98        // Create a serde_json error
99        let json_error = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
100        let error = TlqError::Serialization(json_error);
101        assert!(!error.is_retryable());
102
103        let error_msg = format!("{}", error);
104        assert!(error_msg.contains("Serialization error:"));
105    }
106
107    #[test]
108    fn test_max_retries_exceeded_not_retryable() {
109        let error = TlqError::MaxRetriesExceeded { max_retries: 3 };
110        assert!(!error.is_retryable());
111
112        let error_msg = format!("{}", error);
113        assert_eq!(error_msg, "Max retries exceeded (3) for operation");
114    }
115
116    #[test]
117    fn test_message_too_large_not_retryable() {
118        let error = TlqError::MessageTooLarge { size: 70000 };
119        assert!(!error.is_retryable());
120
121        let error_msg = format!("{}", error);
122        assert_eq!(error_msg, "Message too large: 70000 bytes (max: 65536)");
123    }
124
125    #[test]
126    fn test_error_from_io_error() {
127        let io_error = IoError::new(ErrorKind::PermissionDenied, "Access denied");
128        let tlq_error: TlqError = io_error.into();
129
130        assert!(tlq_error.is_retryable()); // IO errors are retryable
131        assert!(matches!(tlq_error, TlqError::Io(_)));
132    }
133
134    #[test]
135    fn test_error_from_serde_json_error() {
136        let json_error = serde_json::from_str::<serde_json::Value>("{invalid}").unwrap_err();
137        let tlq_error: TlqError = json_error.into();
138
139        assert!(!tlq_error.is_retryable()); // Serialization errors are not retryable
140        assert!(matches!(tlq_error, TlqError::Serialization(_)));
141    }
142
143    #[test]
144    fn test_different_io_error_kinds() {
145        let error_kinds = vec![
146            ErrorKind::NotFound,
147            ErrorKind::PermissionDenied,
148            ErrorKind::ConnectionRefused,
149            ErrorKind::ConnectionReset,
150            ErrorKind::TimedOut,
151            ErrorKind::Interrupted,
152        ];
153
154        for kind in error_kinds {
155            let io_error = IoError::new(kind, format!("{:?} error", kind));
156            let tlq_error = TlqError::Io(io_error);
157
158            // All IO errors should be retryable
159            assert!(tlq_error.is_retryable());
160        }
161    }
162
163    #[test]
164    fn test_server_error_status_codes() {
165        let test_cases = vec![
166            (400, "Bad Request"),
167            (401, "Unauthorized"),
168            (403, "Forbidden"),
169            (404, "Not Found"),
170            (500, "Internal Server Error"),
171            (502, "Bad Gateway"),
172            (503, "Service Unavailable"),
173            (504, "Gateway Timeout"),
174        ];
175
176        for (status, message) in test_cases {
177            let error = TlqError::Server {
178                status,
179                message: message.to_string(),
180            };
181
182            // Server errors should not be retryable
183            assert!(!error.is_retryable());
184
185            let error_msg = format!("{}", error);
186            assert!(error_msg.contains(&status.to_string()));
187            assert!(error_msg.contains(message));
188        }
189    }
190
191    #[test]
192    fn test_error_debug_formatting() {
193        let error = TlqError::Connection("test connection error".to_string());
194        let debug_str = format!("{:?}", error);
195        assert!(debug_str.contains("Connection"));
196        assert!(debug_str.contains("test connection error"));
197    }
198
199    #[test]
200    fn test_result_type_alias() {
201        // Test that our Result type alias works correctly
202        let success: Result<String> = Ok("success".to_string());
203        assert!(success.is_ok());
204        if let Ok(value) = success {
205            assert_eq!(value, "success");
206        }
207
208        let failure: Result<String> = Err(TlqError::Validation("test error".to_string()));
209        assert!(failure.is_err());
210
211        match failure {
212            Err(TlqError::Validation(msg)) => assert_eq!(msg, "test error"),
213            _ => panic!("Expected validation error"),
214        }
215    }
216
217    #[test]
218    fn test_timeout_edge_cases() {
219        // Test various timeout values
220        let timeout_0 = TlqError::Timeout(0);
221        assert!(timeout_0.is_retryable());
222        assert_eq!(format!("{}", timeout_0), "Timeout error after 0ms");
223
224        let timeout_max = TlqError::Timeout(u64::MAX);
225        assert!(timeout_max.is_retryable());
226        assert_eq!(
227            format!("{}", timeout_max),
228            format!("Timeout error after {}ms", u64::MAX)
229        );
230    }
231
232    #[test]
233    fn test_message_size_edge_cases() {
234        // Test various message sizes
235        let size_0 = TlqError::MessageTooLarge { size: 0 };
236        assert_eq!(
237            format!("{}", size_0),
238            "Message too large: 0 bytes (max: 65536)"
239        );
240
241        let size_max = TlqError::MessageTooLarge { size: usize::MAX };
242        assert_eq!(
243            format!("{}", size_max),
244            format!("Message too large: {} bytes (max: 65536)", usize::MAX)
245        );
246
247        let size_just_over = TlqError::MessageTooLarge { size: 65537 };
248        assert_eq!(
249            format!("{}", size_just_over),
250            "Message too large: 65537 bytes (max: 65536)"
251        );
252    }
253
254    #[test]
255    fn test_empty_error_messages() {
256        let connection_error = TlqError::Connection("".to_string());
257        assert_eq!(format!("{}", connection_error), "Connection error: ");
258
259        let validation_error = TlqError::Validation("".to_string());
260        assert_eq!(format!("{}", validation_error), "Validation error: ");
261
262        let server_error = TlqError::Server {
263            status: 500,
264            message: "".to_string(),
265        };
266        assert_eq!(format!("{}", server_error), "Server error: 500 - ");
267    }
268}