Skip to main content

CommonLibrary/Transport/
UnifiedResponse.rs

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