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 Message:Message.into(),
44 Source:None,
45 TransportKind:String::new(),
46 Method:None,
47 CorrelationIdentifier:None,
48 RetryAttempt:0,
49 Context:std::collections::HashMap::new(),
50 }
51 }
52
53 pub fn WithTransportKind(mut self, TransportKind:&str) -> Self {
55 self.TransportKind = TransportKind.to_string();
56 self
57 }
58
59 pub fn WithMethod(mut self, Method:&str) -> Self {
61 self.Method = Some(Method.to_string());
62 self
63 }
64
65 pub fn WithCorrelationIdentifier(mut self, CorrelationIdentifier:&str) -> Self {
67 self.CorrelationIdentifier = Some(CorrelationIdentifier.to_string());
68 self
69 }
70
71 pub fn WithRetryAttempt(mut self, RetryAttempt:u32) -> Self {
73 self.RetryAttempt = RetryAttempt;
74 self
75 }
76
77 pub fn WithContext(mut self, Key:&str, Value:&str) -> Self {
79 self.Context.insert(Key.to_string(), Value.to_string());
80 self
81 }
82
83 pub fn WithSource(mut self, SourceError:impl std::error::Error + Send + Sync + 'static) -> Self {
85 self.Source = Some(Box::new(SourceError));
86 self
87 }
88
89 pub fn IsRetryable(&self) -> bool { self.Code.IsRetryable() }
91
92 pub fn RetryDelayMilliseconds(&self) -> u64 { self.Code.RecommendedRetryDelayMilliseconds() }
94
95 pub fn FullMessage(&self) -> String {
97 let mut MessageText = self.Message.clone();
98
99 if let Some(Method) = &self.Method {
100 MessageText.push_str(&format!(" (method: {})", Method));
101 }
102
103 if let Some(CorrelationIdentifier) = &self.CorrelationIdentifier {
104 MessageText.push_str(&format!(" (correlation_id: {})", CorrelationIdentifier));
105 }
106
107 if !self.TransportKind.is_empty() {
108 MessageText.push_str(&format!(" (transport: {})", self.TransportKind));
109 }
110
111 if self.RetryAttempt > 0 {
112 MessageText.push_str(&format!(" (retry: {})", self.RetryAttempt));
113 }
114
115 if !self.Context.is_empty() {
116 let ContextString = self
117 .Context
118 .iter()
119 .map(|(Key, Value)| format!("{}={}", Key, Value))
120 .collect::<Vec<_>>()
121 .join(", ");
122 MessageText.push_str(&format!(" (context: {{{}}})", ContextString));
123 }
124
125 if let Some(SourceError) = &self.Source {
126 MessageText.push_str(&format!(" (cause: {})", SourceError));
127 }
128
129 MessageText
130 }
131}
132
133impl Clone for TransportError {
134 fn clone(&self) -> Self {
135 Self {
136 Code:self.Code,
137 Message:self.Message.clone(),
138 Source:None,
139 TransportKind:self.TransportKind.clone(),
140 Method:self.Method.clone(),
141 CorrelationIdentifier:self.CorrelationIdentifier.clone(),
142 RetryAttempt:self.RetryAttempt,
143 Context:self.Context.clone(),
144 }
145 }
146}
147
148impl PartialEq for TransportError {
149 fn eq(&self, Other:&Self) -> bool {
150 self.Code == Other.Code
151 && self.Message == Other.Message
152 && self.TransportKind == Other.TransportKind
153 && self.Method == Other.Method
154 && self.CorrelationIdentifier == Other.CorrelationIdentifier
155 && self.RetryAttempt == Other.RetryAttempt
156 && self.Context == Other.Context
157 }
158}
159
160impl Eq for TransportError {}
161
162impl fmt::Display for TransportError {
163 fn fmt(&self, Formatter:&mut fmt::Formatter<'_>) -> fmt::Result { write!(Formatter, "{}", self.FullMessage()) }
164}
165
166impl std::error::Error for TransportError {
167 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
168 self.Source
169 .as_ref()
170 .map(|SourceError| SourceError.as_ref() as &dyn std::error::Error)
171 }
172}
173
174impl TransportError {
176 pub fn Connection(Message:impl Into<String>) -> Self {
178 Self::New(TransportErrorCode::ConnectionFailed, Message).WithTransportKind("unknown")
179 }
180
181 pub fn Timeout(Message:impl Into<String>) -> Self {
183 Self::New(TransportErrorCode::Timeout, Message).WithTransportKind("unknown")
184 }
185
186 pub fn InvalidRequest(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::InvalidRequest, Message) }
188
189 pub fn NotSupported(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::NotSupported, Message) }
191
192 pub fn Remote(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::RemoteError, Message) }
194
195 pub fn Internal(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::InternalError, Message) }
197
198 pub fn CircuitBreakerOpen() -> Self {
200 Self::New(TransportErrorCode::CircuitBreakerOpen, "Circuit breaker is open").WithTransportKind("unknown")
201 }
202
203 pub fn RateLimited(RetryAfterMilliseconds:u64) -> Self {
205 let mut Error = Self::New(TransportErrorCode::RateLimited, "Rate limit exceeded")
206 .WithContext("retry_after_ms", &RetryAfterMilliseconds.to_string());
207 Error
208 .Context
209 .insert("retry_after".to_string(), format!("{}ms", RetryAfterMilliseconds));
210 Error
211 }
212
213 pub fn MessageTooLarge(Size:usize, MaximumSize:usize) -> Self {
215 Self::New(
216 TransportErrorCode::MessageTooLarge,
217 format!("Message size {} exceeds maximum {}", Size, MaximumSize),
218 )
219 .WithContext("size", &Size.to_string())
220 .WithContext("max_size", &MaximumSize.to_string())
221 }
222
223 pub fn NotFound(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::NotFound, Message) }
225
226 pub fn Serialization(Message:impl Into<String>) -> Self {
228 Self::New(TransportErrorCode::SerializationError, Message)
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn TestTransportErrorConstruction() {
238 let Error = TransportError::Connection("Connection refused");
239 assert_eq!(Error.Code, TransportErrorCode::ConnectionFailed);
240 assert!(Error.Message.contains("Connection refused"));
241 }
242
243 #[test]
244 fn TestErrorContext() {
245 let Error = TransportError::New(TransportErrorCode::Timeout, "Request timed out")
246 .WithMethod("ping")
247 .WithCorrelationIdentifier("12345")
248 .WithContext("endpoint", "localhost:50051");
249
250 assert_eq!(Error.Method, Some("ping".to_string()));
251 assert_eq!(Error.CorrelationIdentifier, Some("12345".to_string()));
252 assert_eq!(Error.Context.get("endpoint"), Some(&"localhost:50051".to_string()));
253 }
254
255 #[test]
256 fn TestErrorIsRetryable() {
257 let ConnectionError = TransportError::Connection("Connection failed");
258 assert!(ConnectionError.IsRetryable());
259
260 let InvalidError = TransportError::InvalidRequest("Bad params");
261 assert!(!InvalidError.IsRetryable());
262 }
263
264 #[test]
265 fn TestErrorFullMessage() {
266 let Error = TransportError::Timeout("Operation timed out")
267 .WithMethod("get_file")
268 .WithCorrelationIdentifier("abc-123")
269 .WithTransportKind("grpc");
270
271 let FullMessage = Error.FullMessage();
272 assert!(FullMessage.contains("Operation timed out"));
273 assert!(FullMessage.contains("method: get_file"));
274 assert!(FullMessage.contains("correlation_id: abc-123"));
275 assert!(FullMessage.contains("transport: grpc"));
276 }
277}