CommonLibrary/Transport/
UnifiedResponse.rs1use std::collections::HashMap;
6
7use serde::{Deserialize, Serialize};
8
9use super::{
10 Common::{CorrelationId, SystemTimestampGenerator, Timestamp, TimestampGenerator},
11 TransportStrategy::TransportErrorCode,
12};
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct UnifiedResponse {
17 pub CorrelationIdentifier:CorrelationId,
19
20 pub Success:bool,
22
23 #[serde(skip_serializing_if = "Vec::is_empty")]
25 pub Payload:Vec<u8>,
26
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub Error:Option<ResponseError>,
30
31 #[serde(skip_serializing_if = "HashMap::is_empty")]
33 pub Metadata:HashMap<String, String>,
34
35 pub GeneratedAt:Timestamp,
38}
39
40impl UnifiedResponse {
41 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 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 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 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 pub fn WithMetadataMap(mut self, Metadata:HashMap<String, String>) -> Self {
102 self.Metadata = Metadata;
103
104 self
105 }
106
107 pub fn ErrorCode(&self) -> Option<TransportErrorCode> { self.Error.as_ref().map(|ErrorInfo| ErrorInfo.Code) }
109
110 pub fn IsSuccess(&self) -> bool { self.Success }
112
113 pub fn IsError(&self) -> bool { !self.Success }
115
116 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
136pub struct ResponseError {
137 pub Code:TransportErrorCode,
139
140 pub Message:String,
142
143 #[serde(skip_serializing_if = "HashMap::is_empty")]
145 pub Details:HashMap<String, String>,
146}
147
148impl ResponseError {
149 pub fn New(Code:TransportErrorCode, Message:impl Into<String>) -> Self {
151 Self { Code, Message:Message.into(), Details:HashMap::new() }
152 }
153
154 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}