telltale_runtime/testing/
envelope.rs1use serde::{Deserialize, Serialize};
7
8use crate::effects::RoleId;
9use crate::identifiers::RoleName;
10use crate::testing::clock::WallClock;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct ProtocolEnvelope {
19 pub protocol: String,
21 pub from_role: RoleName,
23 pub from_index: Option<u32>,
25 pub to_role: RoleName,
27 pub to_index: Option<u32>,
29 pub message_type: String,
31 pub sequence: u64,
33 pub timestamp_ns: u64,
35 pub correlation_id: Option<String>,
37 pub payload: Vec<u8>,
39}
40
41impl ProtocolEnvelope {
42 #[must_use]
44 pub fn builder() -> EnvelopeBuilder {
45 EnvelopeBuilder::default()
46 }
47
48 #[must_use]
50 pub fn payload_size(&self) -> usize {
51 self.payload.len()
52 }
53
54 #[must_use]
56 pub fn is_protocol(&self, name: &str) -> bool {
57 self.protocol == name
58 }
59
60 #[must_use]
62 pub fn is_from(&self, role: &RoleName) -> bool {
63 &self.from_role == role
64 }
65
66 #[must_use]
68 pub fn is_to(&self, role: &RoleName) -> bool {
69 &self.to_role == role
70 }
71
72 #[must_use]
74 pub fn routing_key(&self) -> String {
75 match (&self.from_index, &self.to_index) {
76 (Some(fi), Some(ti)) => {
77 format!(
78 "{}.{}[{}].{}[{}]",
79 self.protocol, self.from_role, fi, self.to_role, ti
80 )
81 }
82 (Some(fi), None) => {
83 format!(
84 "{}.{}[{}].{}",
85 self.protocol, self.from_role, fi, self.to_role
86 )
87 }
88 (None, Some(ti)) => {
89 format!(
90 "{}.{}.{}[{}]",
91 self.protocol, self.from_role, self.to_role, ti
92 )
93 }
94 (None, None) => {
95 format!("{}.{}.{}", self.protocol, self.from_role, self.to_role)
96 }
97 }
98 }
99
100 pub fn to_bytes(&self) -> Result<Vec<u8>, EnvelopeError> {
102 bincode::serialize(self).map_err(|e| EnvelopeError::Serialization(e.to_string()))
103 }
104
105 pub fn from_bytes(bytes: &[u8]) -> Result<Self, EnvelopeError> {
107 bincode::deserialize(bytes).map_err(|e| EnvelopeError::Deserialization(e.to_string()))
108 }
109}
110
111#[derive(Debug, Default)]
119pub struct EnvelopeBuilder {
120 protocol: Option<String>,
121 from_role: Option<RoleName>,
122 from_index: Option<u32>,
123 to_role: Option<RoleName>,
124 to_index: Option<u32>,
125 message_type: Option<String>,
126 sequence: u64,
127 timestamp_ns: Option<u64>,
128 correlation_id: Option<String>,
129 payload: Vec<u8>,
130}
131
132impl EnvelopeBuilder {
133 #[must_use]
135 pub fn protocol(mut self, protocol: impl Into<String>) -> Self {
136 self.protocol = Some(protocol.into());
137 self
138 }
139
140 #[must_use]
142 pub fn sender(mut self, role: RoleName) -> Self {
143 self.from_role = Some(role);
144 self
145 }
146
147 #[must_use]
149 pub fn sender_role<R: RoleId>(mut self, role: R) -> Self {
150 self.from_role = Some(role.role_name());
151 self.from_index = role.role_index();
152 self
153 }
154
155 #[must_use]
157 pub fn sender_index(mut self, index: u32) -> Self {
158 self.from_index = Some(index);
159 self
160 }
161
162 #[must_use]
164 pub fn recipient(mut self, role: RoleName) -> Self {
165 self.to_role = Some(role);
166 self
167 }
168
169 #[must_use]
171 pub fn recipient_role<R: RoleId>(mut self, role: R) -> Self {
172 self.to_role = Some(role.role_name());
173 self.to_index = role.role_index();
174 self
175 }
176
177 #[must_use]
179 pub fn recipient_index(mut self, index: u32) -> Self {
180 self.to_index = Some(index);
181 self
182 }
183
184 #[must_use]
186 pub fn message_type(mut self, msg_type: impl Into<String>) -> Self {
187 self.message_type = Some(msg_type.into());
188 self
189 }
190
191 #[must_use]
193 pub fn sequence(mut self, seq: u64) -> Self {
194 self.sequence = seq;
195 self
196 }
197
198 #[must_use]
204 pub fn timestamp(mut self, timestamp_ns: u64) -> Self {
205 self.timestamp_ns = Some(timestamp_ns);
206 self
207 }
208
209 #[must_use]
214 pub fn timestamp_from<C: WallClock>(mut self, clock: &C) -> Self {
215 self.timestamp_ns = Some(clock.now_unix_ns());
216 self
217 }
218
219 #[must_use]
221 pub fn correlation_id(mut self, id: impl Into<String>) -> Self {
222 self.correlation_id = Some(id.into());
223 self
224 }
225
226 #[must_use]
228 pub fn payload(mut self, payload: Vec<u8>) -> Self {
229 self.payload = payload;
230 self
231 }
232
233 pub fn payload_from<T: Serialize>(mut self, msg: &T) -> Result<Self, EnvelopeError> {
235 self.payload =
236 bincode::serialize(msg).map_err(|e| EnvelopeError::Serialization(e.to_string()))?;
237 Ok(self)
238 }
239
240 pub fn build(self) -> Result<ProtocolEnvelope, EnvelopeError> {
242 let protocol = self
243 .protocol
244 .ok_or(EnvelopeError::MissingField(EnvelopeField::Protocol))?;
245 let from_role = self
246 .from_role
247 .ok_or(EnvelopeError::MissingField(EnvelopeField::FromRole))?;
248 let to_role = self
249 .to_role
250 .ok_or(EnvelopeError::MissingField(EnvelopeField::ToRole))?;
251 let message_type = self
252 .message_type
253 .ok_or(EnvelopeError::MissingField(EnvelopeField::MessageType))?;
254
255 let timestamp_ns = self.timestamp_ns.unwrap_or(0);
258
259 Ok(ProtocolEnvelope {
260 protocol,
261 from_role,
262 from_index: self.from_index,
263 to_role,
264 to_index: self.to_index,
265 message_type,
266 sequence: self.sequence,
267 timestamp_ns,
268 correlation_id: self.correlation_id,
269 payload: self.payload,
270 })
271 }
272}
273
274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
276pub enum EnvelopeField {
277 Protocol,
279 FromRole,
281 ToRole,
283 MessageType,
285}
286
287impl std::fmt::Display for EnvelopeField {
288 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
289 match self {
290 EnvelopeField::Protocol => write!(f, "protocol"),
291 EnvelopeField::FromRole => write!(f, "from_role"),
292 EnvelopeField::ToRole => write!(f, "to_role"),
293 EnvelopeField::MessageType => write!(f, "message_type"),
294 }
295 }
296}
297
298#[derive(Debug, thiserror::Error)]
300pub enum EnvelopeError {
301 #[error("Missing required field: {0}")]
303 MissingField(EnvelopeField),
304
305 #[error("Serialization error: {0}")]
307 Serialization(String),
308
309 #[error("Deserialization error: {0}")]
311 Deserialization(String),
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use crate::identifiers::RoleName;
318
319 #[test]
320 fn test_envelope_builder() {
321 let envelope = ProtocolEnvelope::builder()
322 .protocol("TestProtocol")
323 .sender(RoleName::from_static("Client"))
324 .recipient(RoleName::from_static("Server"))
325 .message_type("Request")
326 .sequence(1)
327 .payload(vec![1, 2, 3])
328 .build()
329 .unwrap();
330
331 assert_eq!(envelope.protocol, "TestProtocol");
332 assert_eq!(envelope.from_role.as_str(), "Client");
333 assert_eq!(envelope.to_role.as_str(), "Server");
334 assert_eq!(envelope.message_type, "Request");
335 assert_eq!(envelope.sequence, 1);
336 assert_eq!(envelope.payload, vec![1, 2, 3]);
337 }
338
339 #[test]
340 fn test_routing_key() {
341 let envelope = ProtocolEnvelope::builder()
342 .protocol("Proto")
343 .sender(RoleName::from_static("A"))
344 .recipient(RoleName::from_static("B"))
345 .message_type("Msg")
346 .build()
347 .unwrap();
348
349 assert_eq!(envelope.routing_key(), "Proto.A.B");
350
351 let indexed = ProtocolEnvelope::builder()
352 .protocol("Proto")
353 .sender(RoleName::from_static("Worker"))
354 .sender_index(0)
355 .recipient(RoleName::from_static("Manager"))
356 .message_type("Msg")
357 .build()
358 .unwrap();
359
360 assert_eq!(indexed.routing_key(), "Proto.Worker[0].Manager");
361 }
362
363 #[test]
364 fn test_envelope_roundtrip() {
365 let original = ProtocolEnvelope::builder()
366 .protocol("Test")
367 .sender(RoleName::from_static("A"))
368 .recipient(RoleName::from_static("B"))
369 .message_type("Msg")
370 .payload(vec![1, 2, 3, 4, 5])
371 .build()
372 .unwrap();
373
374 let bytes = original.to_bytes().unwrap();
375 let restored = ProtocolEnvelope::from_bytes(&bytes).unwrap();
376
377 assert_eq!(original.protocol, restored.protocol);
378 assert_eq!(original.payload, restored.payload);
379 }
380}