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			Success:true,
48			Payload,
49			Error:None,
50			Metadata:HashMap::new(),
51			GeneratedAt:SystemTimestampGenerator::Now(),
52		}
53	}
54
55	/// Creates a new error response with the given correlation ID and error.
56	pub fn Failure(CorrelationIdentifier:CorrelationId, Error:ResponseError, Payload:Option<Vec<u8>>) -> Self {
57		Self {
58			CorrelationIdentifier,
59			Success:false,
60			Payload:Payload.unwrap_or_default(),
61			Error:Some(Error),
62			Metadata:HashMap::new(),
63			GeneratedAt:SystemTimestampGenerator::Now(),
64		}
65	}
66
67	/// Creates a new error response from a `TransportError`.
68	pub fn FromTransportError(
69		CorrelationIdentifier:CorrelationId,
70		TransportError:&super::TransportError::TransportError,
71	) -> Self {
72		Self::Failure(
73			CorrelationIdentifier,
74			ResponseError {
75				Code:TransportError.Code,
76				Message:TransportError.Message.clone(),
77				Details:TransportError.Context.clone(),
78			},
79			None,
80		)
81	}
82
83	/// Adds metadata to the response.
84	pub fn WithMetadata(mut self, Key:impl Into<String>, Value:impl Into<String>) -> Self {
85		self.Metadata.insert(Key.into(), Value.into());
86		self
87	}
88
89	/// Sets the entire metadata map.
90	pub fn WithMetadataMap(mut self, Metadata:HashMap<String, String>) -> Self {
91		self.Metadata = Metadata;
92		self
93	}
94
95	/// Gets the error code if this is an error response.
96	pub fn ErrorCode(&self) -> Option<TransportErrorCode> { self.Error.as_ref().map(|ErrorInfo| ErrorInfo.Code) }
97
98	/// Checks if this response is a success.
99	pub fn IsSuccess(&self) -> bool { self.Success }
100
101	/// Checks if this response is an error.
102	pub fn IsError(&self) -> bool { !self.Success }
103
104	/// Validates the response.
105	pub fn Validate(&self) -> Result<(), String> {
106		if self.CorrelationIdentifier.is_empty() {
107			return Err("correlation_id cannot be empty".to_string());
108		}
109
110		if self.Success && self.Error.is_some() {
111			return Err("success response must not have error".to_string());
112		}
113
114		if !self.Success && self.Error.is_none() {
115			return Err("error response must have error field".to_string());
116		}
117
118		Ok(())
119	}
120}
121
122/// Error information within a response.
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124pub struct ResponseError {
125	/// Error code indicating the failure type.
126	pub Code:TransportErrorCode,
127
128	/// Human-readable error message.
129	pub Message:String,
130
131	/// Optional additional details as key-value pairs.
132	#[serde(skip_serializing_if = "HashMap::is_empty")]
133	pub Details:HashMap<String, String>,
134}
135
136impl ResponseError {
137	/// Creates a new `ResponseError` with the given code and message.
138	pub fn New(Code:TransportErrorCode, Message:impl Into<String>) -> Self {
139		Self { Code, Message:Message.into(), Details:HashMap::new() }
140	}
141
142	/// Adds a detail key-value pair to the error.
143	pub fn WithDetail(mut self, Key:impl Into<String>, Value:impl Into<String>) -> Self {
144		self.Details.insert(Key.into(), Value.into());
145		self
146	}
147}
148
149impl std::fmt::Display for ResponseError {
150	fn fmt(&self, Formatter:&mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151		write!(Formatter, "{} (code: {:?})", self.Message, self.Code)?;
152		if !self.Details.is_empty() {
153			let DetailsString:Vec<String> =
154				self.Details.iter().map(|(Key, Value)| format!("{}={}", Key, Value)).collect();
155			write!(Formatter, " [{}]", DetailsString.join(", "))?;
156		}
157		Ok(())
158	}
159}
160
161impl std::error::Error for ResponseError {}
162
163#[cfg(test)]
164mod tests {
165	use TransportErrorCode::ConnectionFailed;
166
167	use super::*;
168	use crate::Transport::TransportStrategy::TransportErrorCode;
169
170	#[test]
171	fn TestUnifiedResponseSuccess() {
172		let Response = UnifiedResponse::Success("req-123".to_string(), b"result".to_vec());
173		assert!(Response.Success);
174		assert_eq!(Response.CorrelationIdentifier, "req-123");
175		assert_eq!(Response.Payload, b"result");
176		assert!(Response.Error.is_none());
177	}
178
179	#[test]
180	fn TestUnifiedResponseError() {
181		let Error = ResponseError::New(ConnectionFailed, "Connection timeout");
182		let Response = UnifiedResponse::Failure("req-456".to_string(), Error, None);
183
184		assert!(!Response.Success);
185		assert_eq!(Response.CorrelationIdentifier, "req-456");
186		assert!(Response.Error.is_some());
187		assert_eq!(Response.Error.as_ref().unwrap().Code, ConnectionFailed);
188	}
189
190	#[test]
191	fn TestUnifiedResponseFromTransportError() {
192		let TransportErrorValue = super::super::TransportError::TransportError::New(ConnectionFailed, "Conn failed")
193			.WithMethod("test.method");
194		let Response = UnifiedResponse::FromTransportError("req-789".to_string(), &TransportErrorValue);
195
196		assert!(!Response.Success);
197		assert_eq!(Response.Error.as_ref().unwrap().Code, ConnectionFailed);
198		assert!(Response.Error.as_ref().unwrap().Message.contains("Conn failed"));
199	}
200
201	#[test]
202	fn TestResponseValidation() {
203		let Response = UnifiedResponse::Success("abc".to_string(), Vec::new());
204		assert!(Response.Validate().is_ok());
205
206		let mut Invalid = Response.clone();
207		Invalid.CorrelationIdentifier = String::new();
208		assert!(Invalid.Validate().is_err());
209
210		let mut Invalid2 = Response.clone();
211		Invalid2.Success = false;
212		Invalid2.Error = None;
213		assert!(Invalid2.Validate().is_err());
214	}
215}