Skip to main content

CommonLibrary/Transport/
UnifiedRequest.rs

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