1use core::time::Duration;
2use std::time::Instant;
3
4#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
41pub enum ErrorSeverity {
42 Warning,
44 Error,
46 Critical,
48 Fatal,
50}
51
52#[derive(thiserror::Error, Debug)]
54pub enum ReconnectTError {
55 #[error("Connection failed: {message}")]
57 ConnectionError {
58 message: String,
59 context: ErrorContext,
60 },
61
62 #[error("Protocol error: {message}")]
64 ProtocolError {
65 message: String,
66 context: ErrorContext,
67 },
68
69 #[error("Configuration error: {message}")]
71 ConfigurationError {
72 message: String,
73 context: ErrorContext,
74 },
75
76 #[error("Timeout error: {timeout_type} timeout of {duration:?} exceeded")]
78 TimeoutError {
79 timeout_type: String,
80 duration: Duration,
81 context: ErrorContext,
82 },
83
84 #[error("Authentication failed: {message}")]
86 AuthenticationError {
87 message: String,
88 context: ErrorContext,
89 },
90
91 #[error("Resource exhausted: {resource_type}")]
93 ResourceExhausted {
94 resource_type: String,
95 context: ErrorContext,
96 },
97
98 #[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 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 ReconnectTError::ReceiveTimeout(_) => ErrorSeverity::Error,
124 ReconnectTError::HandshakeFailed => ErrorSeverity::Error,
125 ReconnectTError::SenderNotConnected => ErrorSeverity::Warning,
126 ReconnectTError::TokioTungsteniteError(e) => {
127 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 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 pub fn is_retryable(&self) -> bool {
155 match self.severity() {
156 ErrorSeverity::Warning | ErrorSeverity::Error => true,
157 ErrorSeverity::Critical => true, ErrorSeverity::Fatal => false,
159 }
160 }
161
162 pub fn requires_connection_reset(&self) -> bool {
164 match self.severity() {
165 ErrorSeverity::Critical => true,
166 _ => false,
167 }
168 }
169
170 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
181impl 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
227impl ReconnectTError {
229 pub fn receive_timeout(duration: Duration) -> Self {
231 Self::ReceiveTimeout(duration)
232 }
233
234 pub fn handshake_failed() -> Self {
236 Self::HandshakeFailed
237 }
238
239 pub fn sender_not_connected() -> Self {
241 Self::SenderNotConnected
242 }
243}
244
245impl 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 ReconnectTError::connection_failed(format!("Tungstenite error: {}", e))
299 }
300 }
301 }
302}