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			Method:Method.into(),
51			Payload:Vec::new(),
52			Metadata:HashMap::new(),
53			CreatedAt:SystemTimestampGenerator::Now(),
54			TransportHint:None,
55		}
56	}
57
58	/// Sets the correlation ID explicitly.
59	pub fn WithCorrelationIdentifier(mut self, CorrelationIdentifier:CorrelationId) -> Self {
60		self.CorrelationIdentifier = Some(CorrelationIdentifier);
61		self
62	}
63
64	/// Sets the binary payload.
65	pub fn WithPayload(mut self, Payload:Vec<u8>) -> Self {
66		self.Payload = Payload;
67		self
68	}
69
70	/// Adds a metadata key-value pair.
71	pub fn WithMetadata(mut self, Key:impl Into<String>, Value:impl Into<String>) -> Self {
72		self.Metadata.insert(Key.into(), Value.into());
73		self
74	}
75
76	/// Sets the entire metadata map.
77	pub fn WithMetadataMap(mut self, Metadata:HashMap<String, String>) -> Self {
78		self.Metadata = Metadata;
79		self
80	}
81
82	/// Sets the request timeout in milliseconds.
83	pub fn WithTimeout(mut self, TimeoutMilliseconds:u64) -> Self {
84		self.Metadata.insert("timeout_ms".to_string(), TimeoutMilliseconds.to_string());
85		self
86	}
87
88	/// Sets the request priority.
89	pub fn WithPriority(mut self, Priority:u32) -> Self {
90		self.Metadata.insert("priority".to_string(), Priority.to_string());
91		self
92	}
93
94	/// Sets the preferred transport type.
95	pub fn WithTransportHint(mut self, TransportKind:TransportType) -> Self {
96		self.TransportHint = Some(TransportKind);
97		self
98	}
99
100	/// Gets the timeout from metadata, if present.
101	pub fn TimeoutMilliseconds(&self) -> Option<u64> {
102		self.Metadata.get("timeout_ms").and_then(|Value| Value.parse().ok())
103	}
104
105	/// Gets the priority from metadata, if present.
106	pub fn Priority(&self) -> Option<u32> { self.Metadata.get("priority").and_then(|Value| Value.parse().ok()) }
107
108	/// Validates the request.
109	pub fn Validate(&self) -> Result<(), String> {
110		if self.Method.is_empty() {
111			return Err("method cannot be empty".to_string());
112		}
113
114		if let Some(Identifier) = &self.CorrelationIdentifier {
115			if Identifier.is_empty() {
116				return Err("correlation_id cannot be empty if specified".to_string());
117			}
118		}
119
120		Ok(())
121	}
122}
123
124#[cfg(test)]
125mod tests {
126	use super::*;
127
128	#[test]
129	fn TestUnifiedRequestCreation() {
130		let Request = UnifiedRequest::New("test.method");
131		assert!(!Request.Method.is_empty());
132		assert!(Request.CorrelationIdentifier.is_some());
133		assert_eq!(Request.Payload, Vec::<u8>::new());
134		assert!(Request.Metadata.is_empty());
135		assert!(Request.TransportHint.is_none());
136	}
137
138	#[test]
139	fn TestUnifiedRequestBuilder() {
140		let Request = UnifiedRequest::New("fileSystem.readFile")
141			.WithPayload(b"{\"path\": \"/tmp/test.txt\"}".to_vec())
142			.WithTimeout(5000)
143			.WithPriority(10)
144			.WithMetadata("traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01")
145			.WithTransportHint(TransportType::Grpc);
146
147		assert_eq!(Request.Method, "fileSystem.readFile");
148		assert_eq!(Request.Payload, b"{\"path\": \"/tmp/test.txt\"}");
149		assert_eq!(Request.TimeoutMilliseconds(), Some(5000));
150		assert_eq!(Request.Priority(), Some(10));
151		assert_eq!(Request.TransportHint, Some(TransportType::Grpc));
152		assert!(Request.Metadata.contains_key("traceparent"));
153	}
154
155	#[test]
156	fn TestUnifiedRequestValidation() {
157		let mut Request = UnifiedRequest::New("valid.method");
158		assert!(Request.Validate().is_ok());
159
160		Request.Method = String::new();
161		assert!(Request.Validate().is_err());
162
163		Request.Method = "valid.method".to_string();
164		Request.CorrelationIdentifier = Some("".to_string());
165		assert!(Request.Validate().is_err());
166	}
167}