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