Skip to main content

CommonLibrary/Transport/
TransportError.rs

1//! # TransportError
2//!
3//! Defines the unified error type for all transport operations.
4
5use std::fmt;
6
7use super::TransportStrategy::TransportErrorCode;
8
9/// Unified transport error.
10#[derive(Debug)]
11pub struct TransportError {
12	/// Error code indicating the type of failure.
13	pub Code:TransportErrorCode,
14
15	/// Human-readable error message.
16	pub Message:String,
17
18	/// Optional underlying/boxed error.
19	pub Source:Option<Box<dyn std::error::Error + Send + Sync>>,
20
21	/// The transport type that generated this error.
22	pub TransportKind:String,
23
24	/// The method being invoked when the error occurred (if applicable).
25	pub Method:Option<String>,
26
27	/// The correlation/request ID for tracing.
28	pub CorrelationIdentifier:Option<String>,
29
30	/// Number of retry attempts before this failure.
31	pub RetryAttempt:u32,
32
33	/// Additional error context as key-value pairs.
34	pub Context:std::collections::HashMap<String, String>,
35}
36
37impl TransportError {
38	/// Creates a new `TransportError` with the given code and message.
39	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	/// Sets the transport type on this error.
60	pub fn WithTransportKind(mut self, TransportKind:&str) -> Self {
61		self.TransportKind = TransportKind.to_string();
62
63		self
64	}
65
66	/// Sets the method name on this error.
67	pub fn WithMethod(mut self, Method:&str) -> Self {
68		self.Method = Some(Method.to_string());
69
70		self
71	}
72
73	/// Sets the correlation/request ID on this error.
74	pub fn WithCorrelationIdentifier(mut self, CorrelationIdentifier:&str) -> Self {
75		self.CorrelationIdentifier = Some(CorrelationIdentifier.to_string());
76
77		self
78	}
79
80	/// Sets the retry attempt count.
81	pub fn WithRetryAttempt(mut self, RetryAttempt:u32) -> Self {
82		self.RetryAttempt = RetryAttempt;
83
84		self
85	}
86
87	/// Adds a context key-value pair to this error.
88	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	/// Sets the underlying source error.
95	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	/// Returns `true` if this error is retryable.
102	pub fn IsRetryable(&self) -> bool { self.Code.IsRetryable() }
103
104	/// Returns the recommended retry delay in milliseconds.
105	pub fn RetryDelayMilliseconds(&self) -> u64 { self.Code.RecommendedRetryDelayMilliseconds() }
106
107	/// Returns the full error message with all context included.
108	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
194/// Convenience constructors for common transport errors.
195impl TransportError {
196	/// Connection error: failed to connect or lost connection.
197	pub fn Connection(Message:impl Into<String>) -> Self {
198		Self::New(TransportErrorCode::ConnectionFailed, Message).WithTransportKind("unknown")
199	}
200
201	/// Timeout error: operation exceeded deadline.
202	pub fn Timeout(Message:impl Into<String>) -> Self {
203		Self::New(TransportErrorCode::Timeout, Message).WithTransportKind("unknown")
204	}
205
206	/// Invalid request error: bad parameters or format.
207	pub fn InvalidRequest(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::InvalidRequest, Message) }
208
209	/// Not supported error: feature not implemented by this transport.
210	pub fn NotSupported(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::NotSupported, Message) }
211
212	/// Remote error: the remote endpoint returned an error.
213	pub fn Remote(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::RemoteError, Message) }
214
215	/// Internal error: something went wrong inside the transport.
216	pub fn Internal(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::InternalError, Message) }
217
218	/// Circuit breaker open error: request rejected due to circuit breaker.
219	pub fn CircuitBreakerOpen() -> Self {
220		Self::New(TransportErrorCode::CircuitBreakerOpen, "Circuit breaker is open").WithTransportKind("unknown")
221	}
222
223	/// Rate limited error: too many requests.
224	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	/// Message too large error.
236	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	/// Not found error: resource or transport not found.
246	pub fn NotFound(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::NotFound, Message) }
247
248	/// Serialization error.
249	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}