Skip to main content

CommonLibrary/Transport/
UnifiedResponse.rs

1#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]
2//! # UnifiedResponse
3//!
4//! A protocol-agnostic response message that works across all transport types.
5
6use std::collections::HashMap;
7
8use serde::{Deserialize, Serialize};
9
10use super::{
11	Common::{CorrelationId, SystemTimestampGenerator, Timestamp, TimestampGenerator},
12	TransportStrategy::TransportErrorCode,
13};
14
15/// A unified response message that can be received over any transport.
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct UnifiedResponse {
18	/// Correlation ID matching the request.
19	pub CorrelationIdentifier:CorrelationId,
20
21	/// Success flag indicating whether the operation completed successfully.
22	pub Success:bool,
23
24	/// Binary payload containing the serialized result (if `Success = true`).
25	#[serde(skip_serializing_if = "Vec::is_empty")]
26	pub Payload:Vec<u8>,
27
28	/// Error information when `Success = false`.
29	#[serde(skip_serializing_if = "Option::is_none")]
30	pub Error:Option<ResponseError>,
31
32	/// Additional response metadata.
33	#[serde(skip_serializing_if = "HashMap::is_empty")]
34	pub Metadata:HashMap<String, String>,
35
36	/// Timestamp when the response was generated (microseconds since Unix
37	/// epoch).
38	pub GeneratedAt:Timestamp,
39}
40
41impl UnifiedResponse {
42	/// Creates a new successful response with the given correlation ID and
43	/// payload.
44	pub fn Success(CorrelationIdentifier:CorrelationId, Payload:Vec<u8>) -> Self {
45		Self {
46			CorrelationIdentifier,
47
48			Success:true,
49
50			Payload,
51
52			Error:None,
53
54			Metadata:HashMap::new(),
55
56			GeneratedAt:SystemTimestampGenerator::Now(),
57		}
58	}
59
60	/// Creates a new error response with the given correlation ID and error.
61	pub fn Failure(CorrelationIdentifier:CorrelationId, Error:ResponseError, Payload:Option<Vec<u8>>) -> Self {
62		Self {
63			CorrelationIdentifier,
64
65			Success:false,
66
67			Payload:Payload.unwrap_or_default(),
68
69			Error:Some(Error),
70
71			Metadata:HashMap::new(),
72
73			GeneratedAt:SystemTimestampGenerator::Now(),
74		}
75	}
76
77	/// Creates a new error response from a `TransportError`.
78	pub fn FromTransportError(
79		CorrelationIdentifier:CorrelationId,
80
81		TransportError:&super::TransportError::TransportError,
82	) -> Self {
83		Self::Failure(
84			CorrelationIdentifier,
85			ResponseError {
86				Code:TransportError.Code,
87				Message:TransportError.Message.clone(),
88				Details:TransportError.Context.clone(),
89			},
90			None,
91		)
92	}
93
94	/// Adds metadata to the response.
95	pub fn WithMetadata(mut self, Key:impl Into<String>, Value:impl Into<String>) -> Self {
96		self.Metadata.insert(Key.into(), Value.into());
97
98		self
99	}
100
101	/// Sets the entire metadata map.
102	pub fn WithMetadataMap(mut self, Metadata:HashMap<String, String>) -> Self {
103		self.Metadata = Metadata;
104
105		self
106	}
107
108	/// Gets the error code if this is an error response.
109	pub fn ErrorCode(&self) -> Option<TransportErrorCode> { self.Error.as_ref().map(|ErrorInfo| ErrorInfo.Code) }
110
111	/// Checks if this response is a success.
112	pub fn IsSuccess(&self) -> bool { self.Success }
113
114	/// Checks if this response is an error.
115	pub fn IsError(&self) -> bool { !self.Success }
116
117	/// Validates the response.
118	pub fn Validate(&self) -> Result<(), String> {
119		if self.CorrelationIdentifier.is_empty() {
120			return Err("correlation_id cannot be empty".to_string());
121		}
122
123		if self.Success && self.Error.is_some() {
124			return Err("success response must not have error".to_string());
125		}
126
127		if !self.Success && self.Error.is_none() {
128			return Err("error response must have error field".to_string());
129		}
130
131		Ok(())
132	}
133}
134
135/// Error information within a response.
136#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
137pub struct ResponseError {
138	/// Error code indicating the failure type.
139	pub Code:TransportErrorCode,
140
141	/// Human-readable error message.
142	pub Message:String,
143
144	/// Optional additional details as key-value pairs.
145	#[serde(skip_serializing_if = "HashMap::is_empty")]
146	pub Details:HashMap<String, String>,
147}
148
149impl ResponseError {
150	/// Creates a new `ResponseError` with the given code and message.
151	pub fn New(Code:TransportErrorCode, Message:impl Into<String>) -> Self {
152		Self { Code, Message:Message.into(), Details:HashMap::new() }
153	}
154
155	/// Adds a detail key-value pair to the error.
156	pub fn WithDetail(mut self, Key:impl Into<String>, Value:impl Into<String>) -> Self {
157		self.Details.insert(Key.into(), Value.into());
158
159		self
160	}
161}
162
163impl std::fmt::Display for ResponseError {
164	fn fmt(&self, Formatter:&mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165		write!(Formatter, "{} (code: {:?})", self.Message, self.Code)?;
166
167		if !self.Details.is_empty() {
168			let DetailsString:Vec<String> =
169				self.Details.iter().map(|(Key, Value)| format!("{}={}", Key, Value)).collect();
170
171			write!(Formatter, " [{}]", DetailsString.join(", "))?;
172		}
173
174		Ok(())
175	}
176}
177
178impl std::error::Error for ResponseError {}
179
180#[cfg(test)]
181mod tests {
182
183	use TransportErrorCode::ConnectionFailed;
184
185	use super::*;
186	use crate::Transport::TransportStrategy::TransportErrorCode;
187
188	#[test]
189	fn TestUnifiedResponseSuccess() {
190		let Response = UnifiedResponse::Success("req-123".to_string(), b"result".to_vec());
191
192		assert!(Response.Success);
193
194		assert_eq!(Response.CorrelationIdentifier, "req-123");
195
196		assert_eq!(Response.Payload, b"result");
197
198		assert!(Response.Error.is_none());
199	}
200
201	#[test]
202	fn TestUnifiedResponseError() {
203		let Error = ResponseError::New(ConnectionFailed, "Connection timeout");
204
205		let Response = UnifiedResponse::Failure("req-456".to_string(), Error, None);
206
207		assert!(!Response.Success);
208
209		assert_eq!(Response.CorrelationIdentifier, "req-456");
210
211		assert!(Response.Error.is_some());
212
213		assert_eq!(Response.Error.as_ref().unwrap().Code, ConnectionFailed);
214	}
215
216	#[test]
217	fn TestUnifiedResponseFromTransportError() {
218		let TransportErrorValue = super::super::TransportError::TransportError::New(ConnectionFailed, "Conn failed")
219			.WithMethod("test.method");
220
221		let Response = UnifiedResponse::FromTransportError("req-789".to_string(), &TransportErrorValue);
222
223		assert!(!Response.Success);
224
225		assert_eq!(Response.Error.as_ref().unwrap().Code, ConnectionFailed);
226
227		assert!(Response.Error.as_ref().unwrap().Message.contains("Conn failed"));
228	}
229
230	#[test]
231	fn TestResponseValidation() {
232		let Response = UnifiedResponse::Success("abc".to_string(), Vec::new());
233
234		assert!(Response.Validate().is_ok());
235
236		let mut Invalid = Response.clone();
237
238		Invalid.CorrelationIdentifier = String::new();
239
240		assert!(Invalid.Validate().is_err());
241
242		let mut Invalid2 = Response.clone();
243
244		Invalid2.Success = false;
245
246		Invalid2.Error = None;
247
248		assert!(Invalid2.Validate().is_err());
249	}
250}