brainwires_agent_network/network/
envelope.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct MessageEnvelope {
11 pub id: Uuid,
13 pub sender: Uuid,
15 pub recipient: MessageTarget,
17 pub payload: Payload,
19 pub timestamp: DateTime<Utc>,
21 pub ttl: Option<u32>,
23 pub correlation_id: Option<Uuid>,
25}
26
27impl MessageEnvelope {
28 pub fn direct(sender: Uuid, recipient: Uuid, payload: impl Into<Payload>) -> Self {
30 Self {
31 id: Uuid::new_v4(),
32 sender,
33 recipient: MessageTarget::Direct(recipient),
34 payload: payload.into(),
35 timestamp: Utc::now(),
36 ttl: None,
37 correlation_id: None,
38 }
39 }
40
41 pub fn broadcast(sender: Uuid, payload: impl Into<Payload>) -> Self {
43 Self {
44 id: Uuid::new_v4(),
45 sender,
46 recipient: MessageTarget::Broadcast,
47 payload: payload.into(),
48 timestamp: Utc::now(),
49 ttl: None,
50 correlation_id: None,
51 }
52 }
53
54 pub fn topic(sender: Uuid, topic: impl Into<String>, payload: impl Into<Payload>) -> Self {
56 Self {
57 id: Uuid::new_v4(),
58 sender,
59 recipient: MessageTarget::Topic(topic.into()),
60 payload: payload.into(),
61 timestamp: Utc::now(),
62 ttl: None,
63 correlation_id: None,
64 }
65 }
66
67 pub fn with_ttl(mut self, ttl: u32) -> Self {
69 self.ttl = Some(ttl);
70 self
71 }
72
73 pub fn with_correlation(mut self, correlation_id: Uuid) -> Self {
75 self.correlation_id = Some(correlation_id);
76 self
77 }
78
79 pub fn reply(&self, sender: Uuid, payload: impl Into<Payload>) -> Self {
81 Self {
82 id: Uuid::new_v4(),
83 sender,
84 recipient: MessageTarget::Direct(self.sender),
85 payload: payload.into(),
86 timestamp: Utc::now(),
87 ttl: None,
88 correlation_id: Some(self.id),
89 }
90 }
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
95pub enum MessageTarget {
96 Direct(Uuid),
98 Broadcast,
100 Topic(String),
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub enum Payload {
107 Json(serde_json::Value),
109 #[serde(with = "base64_bytes")]
111 Binary(Vec<u8>),
112 Text(String),
114}
115
116impl From<serde_json::Value> for Payload {
117 fn from(v: serde_json::Value) -> Self {
118 Payload::Json(v)
119 }
120}
121
122impl From<String> for Payload {
123 fn from(s: String) -> Self {
124 Payload::Text(s)
125 }
126}
127
128impl From<&str> for Payload {
129 fn from(s: &str) -> Self {
130 Payload::Text(s.to_string())
131 }
132}
133
134impl From<Vec<u8>> for Payload {
135 fn from(b: Vec<u8>) -> Self {
136 Payload::Binary(b)
137 }
138}
139
140mod base64_bytes {
142 use base64::Engine;
143 use base64::engine::general_purpose::STANDARD;
144 use serde::{Deserialize, Deserializer, Serialize, Serializer};
145
146 pub fn serialize<S: Serializer>(bytes: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
147 STANDARD.encode(bytes).serialize(s)
148 }
149
150 pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
151 let encoded = String::deserialize(d)?;
152 STANDARD.decode(&encoded).map_err(serde::de::Error::custom)
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn direct_envelope_fields() {
162 let sender = Uuid::new_v4();
163 let recipient = Uuid::new_v4();
164 let env = MessageEnvelope::direct(sender, recipient, "hello");
165
166 assert_eq!(env.sender, sender);
167 assert_eq!(env.recipient, MessageTarget::Direct(recipient));
168 assert!(env.ttl.is_none());
169 assert!(env.correlation_id.is_none());
170 }
171
172 #[test]
173 fn broadcast_envelope() {
174 let sender = Uuid::new_v4();
175 let env = MessageEnvelope::broadcast(sender, "ping");
176 assert_eq!(env.recipient, MessageTarget::Broadcast);
177 }
178
179 #[test]
180 fn topic_envelope() {
181 let sender = Uuid::new_v4();
182 let env = MessageEnvelope::topic(sender, "status-updates", "agent online");
183 assert_eq!(env.recipient, MessageTarget::Topic("status-updates".into()));
184 }
185
186 #[test]
187 fn reply_sets_correlation() {
188 let sender_a = Uuid::new_v4();
189 let sender_b = Uuid::new_v4();
190 let original = MessageEnvelope::direct(sender_a, sender_b, "request");
191 let reply = original.reply(sender_b, "response");
192
193 assert_eq!(reply.sender, sender_b);
194 assert_eq!(reply.recipient, MessageTarget::Direct(sender_a));
195 assert_eq!(reply.correlation_id, Some(original.id));
196 }
197
198 #[test]
199 fn with_ttl() {
200 let env = MessageEnvelope::broadcast(Uuid::new_v4(), "test").with_ttl(5);
201 assert_eq!(env.ttl, Some(5));
202 }
203
204 #[test]
205 fn envelope_serde_roundtrip() {
206 let env = MessageEnvelope::direct(Uuid::new_v4(), Uuid::new_v4(), "hello");
207 let json = serde_json::to_string(&env).unwrap();
208 let deserialized: MessageEnvelope = serde_json::from_str(&json).unwrap();
209 assert_eq!(deserialized.id, env.id);
210 assert_eq!(deserialized.sender, env.sender);
211 }
212
213 #[test]
214 fn binary_payload_serde_roundtrip() {
215 let env =
216 MessageEnvelope::direct(Uuid::new_v4(), Uuid::new_v4(), vec![0xDE, 0xAD, 0xBE, 0xEF]);
217 let json = serde_json::to_string(&env).unwrap();
218 let deserialized: MessageEnvelope = serde_json::from_str(&json).unwrap();
219 match deserialized.payload {
220 Payload::Binary(bytes) => assert_eq!(bytes, vec![0xDE, 0xAD, 0xBE, 0xEF]),
221 _ => panic!("expected Binary payload"),
222 }
223 }
224
225 #[test]
226 fn json_payload_from_value() {
227 let payload: Payload = serde_json::json!({"key": "value"}).into();
228 match payload {
229 Payload::Json(v) => assert_eq!(v["key"], "value"),
230 _ => panic!("expected Json payload"),
231 }
232 }
233}