stream_tungstenite/
errors.rs

1use core::time::Duration;
2use std::time::Instant;
3
4/// Error context providing additional information about when and where an error occurred
5#[derive(Debug, Clone)]
6pub struct ErrorContext {
7    pub timestamp: Instant,
8    pub retry_count: u32,
9    pub connection_id: Option<u64>,
10    pub additional_info: Option<String>,
11}
12
13impl ErrorContext {
14    pub fn new() -> Self {
15        Self {
16            timestamp: Instant::now(),
17            retry_count: 0,
18            connection_id: None,
19            additional_info: None,
20        }
21    }
22
23    pub fn with_retry_count(mut self, retry_count: u32) -> Self {
24        self.retry_count = retry_count;
25        self
26    }
27
28    pub fn with_connection_id(mut self, connection_id: u64) -> Self {
29        self.connection_id = Some(connection_id);
30        self
31    }
32
33    pub fn with_info<S: Into<String>>(mut self, info: S) -> Self {
34        self.additional_info = Some(info.into());
35        self
36    }
37}
38
39/// Severity level of errors for different handling strategies
40#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
41pub enum ErrorSeverity {
42    /// Recoverable errors that can be retried immediately
43    Warning,
44    /// Errors that require retry with backoff
45    Error,
46    /// Critical errors that may require connection reset
47    Critical,
48    /// Fatal errors that should stop reconnection attempts
49    Fatal,
50}
51
52/// Simplified error types for WebSocket reconnection operations
53#[derive(thiserror::Error, Debug)]
54pub enum ReconnectTError {
55    /// Connection-related errors
56    #[error("Connection failed: {message}")]
57    ConnectionError {
58        message: String,
59        context: ErrorContext,
60    },
61
62    /// Protocol-related errors
63    #[error("Protocol error: {message}")]
64    ProtocolError {
65        message: String,
66        context: ErrorContext,
67    },
68
69    /// Configuration errors
70    #[error("Configuration error: {message}")]
71    ConfigurationError {
72        message: String,
73        context: ErrorContext,
74    },
75
76    /// Timeout errors with specific timeout information
77    #[error("Timeout error: {timeout_type} timeout of {duration:?} exceeded")]
78    TimeoutError {
79        timeout_type: String,
80        duration: Duration,
81        context: ErrorContext,
82    },
83
84    /// Authentication/authorization errors
85    #[error("Authentication failed: {message}")]
86    AuthenticationError {
87        message: String,
88        context: ErrorContext,
89    },
90
91    /// Resource exhaustion errors
92    #[error("Resource exhausted: {resource_type}")]
93    ResourceExhausted {
94        resource_type: String,
95        context: ErrorContext,
96    },
97
98    /// Legacy errors for backward compatibility
99    #[error("receive timeout: {0:?}")]
100    ReceiveTimeout(Duration),
101
102    #[error("handshake failed")]
103    HandshakeFailed,
104
105    #[error("sender not connected")]
106    SenderNotConnected,
107
108    #[error("tokio_tungstenite error: {0}")]
109    TokioTungsteniteError(#[from] tokio_tungstenite::tungstenite::Error),
110}
111
112impl ReconnectTError {
113    /// Get the severity level of the error - simplified logic
114    pub fn severity(&self) -> ErrorSeverity {
115        match self {
116            ReconnectTError::ConnectionError { .. } => ErrorSeverity::Error,
117            ReconnectTError::ProtocolError { .. } => ErrorSeverity::Critical,
118            ReconnectTError::ConfigurationError { .. } => ErrorSeverity::Fatal,
119            ReconnectTError::TimeoutError { .. } => ErrorSeverity::Error,
120            ReconnectTError::AuthenticationError { .. } => ErrorSeverity::Critical,
121            ReconnectTError::ResourceExhausted { .. } => ErrorSeverity::Critical,
122            // Legacy errors
123            ReconnectTError::ReceiveTimeout(_) => ErrorSeverity::Error,
124            ReconnectTError::HandshakeFailed => ErrorSeverity::Error,
125            ReconnectTError::SenderNotConnected => ErrorSeverity::Warning,
126            ReconnectTError::TokioTungsteniteError(e) => {
127                // Simplified classification
128                match e {
129                    tokio_tungstenite::tungstenite::Error::ConnectionClosed => ErrorSeverity::Warning,
130                    tokio_tungstenite::tungstenite::Error::AlreadyClosed => ErrorSeverity::Warning,
131                    tokio_tungstenite::tungstenite::Error::Url(_) => ErrorSeverity::Fatal,
132                    tokio_tungstenite::tungstenite::Error::HttpFormat(_) => ErrorSeverity::Fatal,
133                    tokio_tungstenite::tungstenite::Error::Protocol(_) => ErrorSeverity::Critical,
134                    _ => ErrorSeverity::Error,
135                }
136            }
137        }
138    }
139
140    /// Get the error context if available
141    pub fn context(&self) -> Option<&ErrorContext> {
142        match self {
143            ReconnectTError::ConnectionError { context, .. } => Some(context),
144            ReconnectTError::ProtocolError { context, .. } => Some(context),
145            ReconnectTError::ConfigurationError { context, .. } => Some(context),
146            ReconnectTError::TimeoutError { context, .. } => Some(context),
147            ReconnectTError::AuthenticationError { context, .. } => Some(context),
148            ReconnectTError::ResourceExhausted { context, .. } => Some(context),
149            _ => None,
150        }
151    }
152
153    /// Check if this error is retryable based on its severity
154    pub fn is_retryable(&self) -> bool {
155        match self.severity() {
156            ErrorSeverity::Warning | ErrorSeverity::Error => true,
157            ErrorSeverity::Critical => true, // Critical errors may be retryable after connection reset
158            ErrorSeverity::Fatal => false,
159        }
160    }
161
162    /// Check if this error requires a connection reset
163    pub fn requires_connection_reset(&self) -> bool {
164        match self.severity() {
165            ErrorSeverity::Critical => true,
166            _ => false,
167        }
168    }
169
170    /// Get the recommended retry delay based on error type and context
171    pub fn recommended_retry_delay(&self) -> Option<Duration> {
172        match self.severity() {
173            ErrorSeverity::Warning => Some(Duration::from_millis(100)),
174            ErrorSeverity::Error => Some(Duration::from_secs(1)),
175            ErrorSeverity::Critical => Some(Duration::from_secs(5)),
176            ErrorSeverity::Fatal => None,
177        }
178    }
179}
180
181// Convenience constructors for common error patterns
182impl ReconnectTError {
183    pub fn connection_failed<S: Into<String>>(message: S) -> Self {
184        Self::ConnectionError {
185            message: message.into(),
186            context: ErrorContext::new(),
187        }
188    }
189
190    pub fn protocol_error<S: Into<String>>(message: S) -> Self {
191        Self::ProtocolError {
192            message: message.into(),
193            context: ErrorContext::new(),
194        }
195    }
196
197    pub fn timeout_error<S: Into<String>>(timeout_type: S, duration: Duration) -> Self {
198        Self::TimeoutError {
199            timeout_type: timeout_type.into(),
200            duration,
201            context: ErrorContext::new(),
202        }
203    }
204
205    pub fn auth_error<S: Into<String>>(message: S) -> Self {
206        Self::AuthenticationError {
207            message: message.into(),
208            context: ErrorContext::new(),
209        }
210    }
211
212    pub fn config_error<S: Into<String>>(message: S) -> Self {
213        Self::ConfigurationError {
214            message: message.into(),
215            context: ErrorContext::new(),
216        }
217    }
218
219    pub fn resource_exhausted<S: Into<String>>(resource_type: S) -> Self {
220        Self::ResourceExhausted {
221            resource_type: resource_type.into(),
222            context: ErrorContext::new(),
223        }
224    }
225}
226
227// Legacy error constructors for backward compatibility
228impl ReconnectTError {
229    /// Create a legacy receive timeout error
230    pub fn receive_timeout(duration: Duration) -> Self {
231        Self::ReceiveTimeout(duration)
232    }
233
234    /// Create a legacy handshake failed error
235    pub fn handshake_failed() -> Self {
236        Self::HandshakeFailed
237    }
238
239    /// Create a legacy sender not connected error
240    pub fn sender_not_connected() -> Self {
241        Self::SenderNotConnected
242    }
243}
244
245// Manual Clone implementation since tungstenite::Error doesn't implement Clone
246impl Clone for ReconnectTError {
247    fn clone(&self) -> Self {
248        match self {
249            ReconnectTError::ConnectionError { message, context } => {
250                ReconnectTError::ConnectionError {
251                    message: message.clone(),
252                    context: context.clone(),
253                }
254            }
255            ReconnectTError::ProtocolError { message, context } => {
256                ReconnectTError::ProtocolError {
257                    message: message.clone(),
258                    context: context.clone(),
259                }
260            }
261            ReconnectTError::ConfigurationError { message, context } => {
262                ReconnectTError::ConfigurationError {
263                    message: message.clone(),
264                    context: context.clone(),
265                }
266            }
267            ReconnectTError::TimeoutError { timeout_type, duration, context } => {
268                ReconnectTError::TimeoutError {
269                    timeout_type: timeout_type.clone(),
270                    duration: *duration,
271                    context: context.clone(),
272                }
273            }
274            ReconnectTError::AuthenticationError { message, context } => {
275                ReconnectTError::AuthenticationError {
276                    message: message.clone(),
277                    context: context.clone(),
278                }
279            }
280            ReconnectTError::ResourceExhausted { resource_type, context } => {
281                ReconnectTError::ResourceExhausted {
282                    resource_type: resource_type.clone(),
283                    context: context.clone(),
284                }
285            }
286            ReconnectTError::ReceiveTimeout(duration) => {
287                ReconnectTError::ReceiveTimeout(*duration)
288            }
289            ReconnectTError::HandshakeFailed => {
290                ReconnectTError::HandshakeFailed
291            }
292            ReconnectTError::SenderNotConnected => {
293                ReconnectTError::SenderNotConnected
294            }
295            ReconnectTError::TokioTungsteniteError(e) => {
296                // Since tungstenite::Error doesn't implement Clone, we'll convert to string and back
297                // This is not ideal but necessary for compatibility
298                ReconnectTError::connection_failed(format!("Tungstenite error: {}", e))
299            }
300        }
301    }
302}