Skip to main content

CommonLibrary/Transport/
UnifiedRequest.rs

1//! # UnifiedRequest
2//!
3//! A protocol-agnostic request message that works across all transport types.
4
5use std::collections::HashMap;
6
7use serde::{Deserialize, Serialize};
8
9use super::Common::{
10	CorrelationId,
11	CorrelationIdGenerator,
12	SystemTimestampGenerator,
13	Timestamp,
14	TimestampGenerator,
15	TransportType,
16	UuidCorrelationIdGenerator,
17};
18
19/// A unified request message that can be sent over any transport.
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21pub struct UnifiedRequest {
22	/// Unique correlation ID for request/response matching.
23	#[serde(skip_serializing_if = "Option::is_none")]
24	pub CorrelationIdentifier:Option<CorrelationId>,
25
26	/// The method to invoke, using dot notation (e.g., "fileSystem.readFile").
27	pub Method:String,
28
29	/// Binary payload containing serialized parameters for the method.
30	pub Payload:Vec<u8>,
31
32	/// Optional metadata for the request.
33	#[serde(skip_serializing_if = "HashMap::is_empty")]
34	pub Metadata:HashMap<String, String>,
35
36	/// Timestamp when the request was created (microseconds since Unix epoch).
37	pub CreatedAt:Timestamp,
38
39	/// Optional hint for preferred transport type.
40	#[serde(skip_serializing_if = "Option::is_none")]
41	pub TransportHint:Option<TransportType>,
42}
43
44impl UnifiedRequest {
45	/// Creates a new `UnifiedRequest` with the given method.
46	pub fn New(Method:impl Into<String>) -> Self {
47		Self {
48			CorrelationIdentifier:Some(UuidCorrelationIdGenerator::Generate()),
49
50			Method:Method.into(),
51
52			Payload:Vec::new(),
53
54			Metadata:HashMap::new(),
55
56			CreatedAt:SystemTimestampGenerator::Now(),
57
58			TransportHint:None,
59		}
60	}
61
62	/// Sets the correlation ID explicitly.
63	pub fn WithCorrelationIdentifier(mut self, CorrelationIdentifier:CorrelationId) -> Self {
64		self.CorrelationIdentifier = Some(CorrelationIdentifier);
65
66		self
67	}
68
69	/// Sets the binary payload.
70	pub fn WithPayload(mut self, Payload:Vec<u8>) -> Self {
71		self.Payload = Payload;
72
73		self
74	}
75
76	/// Adds a metadata key-value pair.
77	pub fn WithMetadata(mut self, Key:impl Into<String>, Value:impl Into<String>) -> Self {
78		self.Metadata.insert(Key.into(), Value.into());
79
80		self
81	}
82
83	/// Sets the entire metadata map.
84	pub fn WithMetadataMap(mut self, Metadata:HashMap<String, String>) -> Self {
85		self.Metadata = Metadata;
86
87		self
88	}
89
90	/// Sets the request timeout in milliseconds.
91	pub fn WithTimeout(mut self, TimeoutMilliseconds:u64) -> Self {
92		self.Metadata.insert("timeout_ms".to_string(), TimeoutMilliseconds.to_string());
93
94		self
95	}
96
97	/// Sets the request priority.
98	pub fn WithPriority(mut self, Priority:u32) -> Self {
99		self.Metadata.insert("priority".to_string(), Priority.to_string());
100
101		self
102	}
103
104	/// Sets the preferred transport type.
105	pub fn WithTransportHint(mut self, TransportKind:TransportType) -> Self {
106		self.TransportHint = Some(TransportKind);
107
108		self
109	}
110
111	/// Gets the timeout from metadata, if present.
112	pub fn TimeoutMilliseconds(&self) -> Option<u64> {
113		self.Metadata.get("timeout_ms").and_then(|Value| Value.parse().ok())
114	}
115
116	/// Gets the priority from metadata, if present.
117	pub fn Priority(&self) -> Option<u32> { self.Metadata.get("priority").and_then(|Value| Value.parse().ok()) }
118
119	/// Validates the request.
120	pub fn Validate(&self) -> Result<(), String> {
121		if self.Method.is_empty() {
122			return Err("method cannot be empty".to_string());
123		}
124
125		if let Some(Identifier) = &self.CorrelationIdentifier {
126			if Identifier.is_empty() {
127				return Err("correlation_id cannot be empty if specified".to_string());
128			}
129		}
130
131		Ok(())
132	}
133}
134
135#[cfg(test)]
136mod tests {
137
138	use super::*;
139
140	#[test]
141	fn TestUnifiedRequestCreation() {
142		let Request = UnifiedRequest::New("test.method");
143
144		assert!(!Request.Method.is_empty());
145
146		assert!(Request.CorrelationIdentifier.is_some());
147
148		assert_eq!(Request.Payload, Vec::<u8>::new());
149
150		assert!(Request.Metadata.is_empty());
151
152		assert!(Request.TransportHint.is_none());
153	}
154
155	#[test]
156	fn TestUnifiedRequestBuilder() {
157		let Request = UnifiedRequest::New("fileSystem.readFile")
158			.WithPayload(b"{\"path\": \"/tmp/test.txt\"}".to_vec())
159			.WithTimeout(5000)
160			.WithPriority(10)
161			.WithMetadata("traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01")
162			.WithTransportHint(TransportType::Grpc);
163
164		assert_eq!(Request.Method, "fileSystem.readFile");
165
166		assert_eq!(Request.Payload, b"{\"path\": \"/tmp/test.txt\"}");
167
168		assert_eq!(Request.TimeoutMilliseconds(), Some(5000));
169
170		assert_eq!(Request.Priority(), Some(10));
171
172		assert_eq!(Request.TransportHint, Some(TransportType::Grpc));
173
174		assert!(Request.Metadata.contains_key("traceparent"));
175	}
176
177	#[test]
178	fn TestUnifiedRequestValidation() {
179		let mut Request = UnifiedRequest::New("valid.method");
180
181		assert!(Request.Validate().is_ok());
182
183		Request.Method = String::new();
184
185		assert!(Request.Validate().is_err());
186
187		Request.Method = "valid.method".to_string();
188
189		Request.CorrelationIdentifier = Some("".to_string());
190
191		assert!(Request.Validate().is_err());
192	}
193}