CommonLibrary/Transport/
TransportError.rs1use std::fmt;
6
7use super::TransportStrategy::TransportErrorCode;
8
9#[derive(Debug)]
11pub struct TransportError {
12 pub Code:TransportErrorCode,
14
15 pub Message:String,
17
18 pub Source:Option<Box<dyn std::error::Error + Send + Sync>>,
20
21 pub TransportKind:String,
23
24 pub Method:Option<String>,
26
27 pub CorrelationIdentifier:Option<String>,
29
30 pub RetryAttempt:u32,
32
33 pub Context:std::collections::HashMap<String, String>,
35}
36
37impl TransportError {
38 pub fn New(Code:TransportErrorCode, Message:impl Into<String>) -> Self {
40 Self {
41 Code,
42
43 Message:Message.into(),
44
45 Source:None,
46
47 TransportKind:String::new(),
48
49 Method:None,
50
51 CorrelationIdentifier:None,
52
53 RetryAttempt:0,
54
55 Context:std::collections::HashMap::new(),
56 }
57 }
58
59 pub fn WithTransportKind(mut self, TransportKind:&str) -> Self {
61 self.TransportKind = TransportKind.to_string();
62
63 self
64 }
65
66 pub fn WithMethod(mut self, Method:&str) -> Self {
68 self.Method = Some(Method.to_string());
69
70 self
71 }
72
73 pub fn WithCorrelationIdentifier(mut self, CorrelationIdentifier:&str) -> Self {
75 self.CorrelationIdentifier = Some(CorrelationIdentifier.to_string());
76
77 self
78 }
79
80 pub fn WithRetryAttempt(mut self, RetryAttempt:u32) -> Self {
82 self.RetryAttempt = RetryAttempt;
83
84 self
85 }
86
87 pub fn WithContext(mut self, Key:&str, Value:&str) -> Self {
89 self.Context.insert(Key.to_string(), Value.to_string());
90
91 self
92 }
93
94 pub fn WithSource(mut self, SourceError:impl std::error::Error + Send + Sync + 'static) -> Self {
96 self.Source = Some(Box::new(SourceError));
97
98 self
99 }
100
101 pub fn IsRetryable(&self) -> bool { self.Code.IsRetryable() }
103
104 pub fn RetryDelayMilliseconds(&self) -> u64 { self.Code.RecommendedRetryDelayMilliseconds() }
106
107 pub fn FullMessage(&self) -> String {
109 let mut MessageText = self.Message.clone();
110
111 if let Some(Method) = &self.Method {
112 MessageText.push_str(&format!(" (method: {})", Method));
113 }
114
115 if let Some(CorrelationIdentifier) = &self.CorrelationIdentifier {
116 MessageText.push_str(&format!(" (correlation_id: {})", CorrelationIdentifier));
117 }
118
119 if !self.TransportKind.is_empty() {
120 MessageText.push_str(&format!(" (transport: {})", self.TransportKind));
121 }
122
123 if self.RetryAttempt > 0 {
124 MessageText.push_str(&format!(" (retry: {})", self.RetryAttempt));
125 }
126
127 if !self.Context.is_empty() {
128 let ContextString = self
129 .Context
130 .iter()
131 .map(|(Key, Value)| format!("{}={}", Key, Value))
132 .collect::<Vec<_>>()
133 .join(", ");
134
135 MessageText.push_str(&format!(" (context: {{{}}})", ContextString));
136 }
137
138 if let Some(SourceError) = &self.Source {
139 MessageText.push_str(&format!(" (cause: {})", SourceError));
140 }
141
142 MessageText
143 }
144}
145
146impl Clone for TransportError {
147 fn clone(&self) -> Self {
148 Self {
149 Code:self.Code,
150
151 Message:self.Message.clone(),
152
153 Source:None,
154
155 TransportKind:self.TransportKind.clone(),
156
157 Method:self.Method.clone(),
158
159 CorrelationIdentifier:self.CorrelationIdentifier.clone(),
160
161 RetryAttempt:self.RetryAttempt,
162
163 Context:self.Context.clone(),
164 }
165 }
166}
167
168impl PartialEq for TransportError {
169 fn eq(&self, Other:&Self) -> bool {
170 self.Code == Other.Code
171 && self.Message == Other.Message
172 && self.TransportKind == Other.TransportKind
173 && self.Method == Other.Method
174 && self.CorrelationIdentifier == Other.CorrelationIdentifier
175 && self.RetryAttempt == Other.RetryAttempt
176 && self.Context == Other.Context
177 }
178}
179
180impl Eq for TransportError {}
181
182impl fmt::Display for TransportError {
183 fn fmt(&self, Formatter:&mut fmt::Formatter<'_>) -> fmt::Result { write!(Formatter, "{}", self.FullMessage()) }
184}
185
186impl std::error::Error for TransportError {
187 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
188 self.Source
189 .as_ref()
190 .map(|SourceError| SourceError.as_ref() as &dyn std::error::Error)
191 }
192}
193
194impl TransportError {
196 pub fn Connection(Message:impl Into<String>) -> Self {
198 Self::New(TransportErrorCode::ConnectionFailed, Message).WithTransportKind("unknown")
199 }
200
201 pub fn Timeout(Message:impl Into<String>) -> Self {
203 Self::New(TransportErrorCode::Timeout, Message).WithTransportKind("unknown")
204 }
205
206 pub fn InvalidRequest(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::InvalidRequest, Message) }
208
209 pub fn NotSupported(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::NotSupported, Message) }
211
212 pub fn Remote(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::RemoteError, Message) }
214
215 pub fn Internal(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::InternalError, Message) }
217
218 pub fn CircuitBreakerOpen() -> Self {
220 Self::New(TransportErrorCode::CircuitBreakerOpen, "Circuit breaker is open").WithTransportKind("unknown")
221 }
222
223 pub fn RateLimited(RetryAfterMilliseconds:u64) -> Self {
225 let mut Error = Self::New(TransportErrorCode::RateLimited, "Rate limit exceeded")
226 .WithContext("retry_after_ms", &RetryAfterMilliseconds.to_string());
227
228 Error
229 .Context
230 .insert("retry_after".to_string(), format!("{}ms", RetryAfterMilliseconds));
231
232 Error
233 }
234
235 pub fn MessageTooLarge(Size:usize, MaximumSize:usize) -> Self {
237 Self::New(
238 TransportErrorCode::MessageTooLarge,
239 format!("Message size {} exceeds maximum {}", Size, MaximumSize),
240 )
241 .WithContext("size", &Size.to_string())
242 .WithContext("max_size", &MaximumSize.to_string())
243 }
244
245 pub fn NotFound(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::NotFound, Message) }
247
248 pub fn Serialization(Message:impl Into<String>) -> Self {
250 Self::New(TransportErrorCode::SerializationError, Message)
251 }
252}
253
254#[cfg(test)]
255mod tests {
256
257 use super::*;
258
259 #[test]
260 fn TestTransportErrorConstruction() {
261 let Error = TransportError::Connection("Connection refused");
262
263 assert_eq!(Error.Code, TransportErrorCode::ConnectionFailed);
264
265 assert!(Error.Message.contains("Connection refused"));
266 }
267
268 #[test]
269 fn TestErrorContext() {
270 let Error = TransportError::New(TransportErrorCode::Timeout, "Request timed out")
271 .WithMethod("ping")
272 .WithCorrelationIdentifier("12345")
273 .WithContext("endpoint", "localhost:50051");
274
275 assert_eq!(Error.Method, Some("ping".to_string()));
276
277 assert_eq!(Error.CorrelationIdentifier, Some("12345".to_string()));
278
279 assert_eq!(Error.Context.get("endpoint"), Some(&"localhost:50051".to_string()));
280 }
281
282 #[test]
283 fn TestErrorIsRetryable() {
284 let ConnectionError = TransportError::Connection("Connection failed");
285
286 assert!(ConnectionError.IsRetryable());
287
288 let InvalidError = TransportError::InvalidRequest("Bad params");
289
290 assert!(!InvalidError.IsRetryable());
291 }
292
293 #[test]
294 fn TestErrorFullMessage() {
295 let Error = TransportError::Timeout("Operation timed out")
296 .WithMethod("get_file")
297 .WithCorrelationIdentifier("abc-123")
298 .WithTransportKind("grpc");
299
300 let FullMessage = Error.FullMessage();
301
302 assert!(FullMessage.contains("Operation timed out"));
303
304 assert!(FullMessage.contains("method: get_file"));
305
306 assert!(FullMessage.contains("correlation_id: abc-123"));
307
308 assert!(FullMessage.contains("transport: grpc"));
309 }
310}