Skip to main content

CommonLibrary/Transport/
TransportError.rs

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