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			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	/// Sets the transport type on this error.
54	pub fn WithTransportKind(mut self, TransportKind:&str) -> Self {
55		self.TransportKind = TransportKind.to_string();
56		self
57	}
58
59	/// Sets the method name on this error.
60	pub fn WithMethod(mut self, Method:&str) -> Self {
61		self.Method = Some(Method.to_string());
62		self
63	}
64
65	/// Sets the correlation/request ID on this error.
66	pub fn WithCorrelationIdentifier(mut self, CorrelationIdentifier:&str) -> Self {
67		self.CorrelationIdentifier = Some(CorrelationIdentifier.to_string());
68		self
69	}
70
71	/// Sets the retry attempt count.
72	pub fn WithRetryAttempt(mut self, RetryAttempt:u32) -> Self {
73		self.RetryAttempt = RetryAttempt;
74		self
75	}
76
77	/// Adds a context key-value pair to this error.
78	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	/// Sets the underlying source error.
84	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	/// Returns `true` if this error is retryable.
90	pub fn IsRetryable(&self) -> bool { self.Code.IsRetryable() }
91
92	/// Returns the recommended retry delay in milliseconds.
93	pub fn RetryDelayMilliseconds(&self) -> u64 { self.Code.RecommendedRetryDelayMilliseconds() }
94
95	/// Returns the full error message with all context included.
96	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
174/// Convenience constructors for common transport errors.
175impl TransportError {
176	/// Connection error: failed to connect or lost connection.
177	pub fn Connection(Message:impl Into<String>) -> Self {
178		Self::New(TransportErrorCode::ConnectionFailed, Message).WithTransportKind("unknown")
179	}
180
181	/// Timeout error: operation exceeded deadline.
182	pub fn Timeout(Message:impl Into<String>) -> Self {
183		Self::New(TransportErrorCode::Timeout, Message).WithTransportKind("unknown")
184	}
185
186	/// Invalid request error: bad parameters or format.
187	pub fn InvalidRequest(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::InvalidRequest, Message) }
188
189	/// Not supported error: feature not implemented by this transport.
190	pub fn NotSupported(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::NotSupported, Message) }
191
192	/// Remote error: the remote endpoint returned an error.
193	pub fn Remote(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::RemoteError, Message) }
194
195	/// Internal error: something went wrong inside the transport.
196	pub fn Internal(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::InternalError, Message) }
197
198	/// Circuit breaker open error: request rejected due to circuit breaker.
199	pub fn CircuitBreakerOpen() -> Self {
200		Self::New(TransportErrorCode::CircuitBreakerOpen, "Circuit breaker is open").WithTransportKind("unknown")
201	}
202
203	/// Rate limited error: too many requests.
204	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	/// Message too large error.
214	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	/// Not found error: resource or transport not found.
224	pub fn NotFound(Message:impl Into<String>) -> Self { Self::New(TransportErrorCode::NotFound, Message) }
225
226	/// Serialization error.
227	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}