Skip to main content

telltale_runtime/testing/
envelope.rs

1//! Protocol message envelope for structured message passing
2//!
3//! Envelopes wrap protocol messages with metadata for routing,
4//! debugging, and simulation purposes.
5
6use serde::{Deserialize, Serialize};
7
8use crate::effects::RoleId;
9use crate::identifiers::RoleName;
10use crate::testing::clock::WallClock;
11
12/// A protocol message envelope containing metadata and payload.
13///
14/// Envelopes provide a standard wrapper for messages that includes
15/// routing information and can be inspected without deserializing
16/// the payload.
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct ProtocolEnvelope {
19    /// Name of the protocol this message belongs to.
20    pub protocol: String,
21    /// Role sending the message.
22    pub from_role: RoleName,
23    /// Role index if the sender is a parameterized role.
24    pub from_index: Option<u32>,
25    /// Role receiving the message.
26    pub to_role: RoleName,
27    /// Role index if the receiver is a parameterized role.
28    pub to_index: Option<u32>,
29    /// Type name of the message payload.
30    pub message_type: String,
31    /// Sequence number for ordering (per sender-receiver pair).
32    pub sequence: u64,
33    /// Timestamp when the message was created (nanoseconds since epoch).
34    pub timestamp_ns: u64,
35    /// Correlation ID for tracing across roles.
36    pub correlation_id: Option<String>,
37    /// The serialized message payload.
38    pub payload: Vec<u8>,
39}
40
41impl ProtocolEnvelope {
42    /// Create a new envelope builder.
43    #[must_use]
44    pub fn builder() -> EnvelopeBuilder {
45        EnvelopeBuilder::default()
46    }
47
48    /// Get the payload size in bytes.
49    #[must_use]
50    pub fn payload_size(&self) -> usize {
51        self.payload.len()
52    }
53
54    /// Check if this envelope is for a specific protocol.
55    #[must_use]
56    pub fn is_protocol(&self, name: &str) -> bool {
57        self.protocol == name
58    }
59
60    /// Check if this envelope is from a specific role.
61    #[must_use]
62    pub fn is_from(&self, role: &RoleName) -> bool {
63        &self.from_role == role
64    }
65
66    /// Check if this envelope is to a specific role.
67    #[must_use]
68    pub fn is_to(&self, role: &RoleName) -> bool {
69        &self.to_role == role
70    }
71
72    /// Create a routing key for this envelope (useful for message queues).
73    #[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    /// Serialize the envelope to bytes.
101    pub fn to_bytes(&self) -> Result<Vec<u8>, EnvelopeError> {
102        bincode::serialize(self).map_err(|e| EnvelopeError::Serialization(e.to_string()))
103    }
104
105    /// Deserialize an envelope from bytes.
106    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/// Builder for creating protocol envelopes.
112///
113/// # Determinism
114///
115/// By default, envelopes are created with `timestamp_ns = 0` for deterministic
116/// simulation. Use `timestamp()` to set an explicit timestamp, or
117/// `timestamp_now()` for production use with real wall-clock time.
118#[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    /// Set the protocol name.
134    #[must_use]
135    pub fn protocol(mut self, protocol: impl Into<String>) -> Self {
136        self.protocol = Some(protocol.into());
137        self
138    }
139
140    /// Set the sender role.
141    #[must_use]
142    pub fn sender(mut self, role: RoleName) -> Self {
143        self.from_role = Some(role);
144        self
145    }
146
147    /// Set the sender role from a typed role identifier.
148    #[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    /// Set the sender role index.
156    #[must_use]
157    pub fn sender_index(mut self, index: u32) -> Self {
158        self.from_index = Some(index);
159        self
160    }
161
162    /// Set the receiver role.
163    #[must_use]
164    pub fn recipient(mut self, role: RoleName) -> Self {
165        self.to_role = Some(role);
166        self
167    }
168
169    /// Set the receiver role from a typed role identifier.
170    #[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    /// Set the receiver role index.
178    #[must_use]
179    pub fn recipient_index(mut self, index: u32) -> Self {
180        self.to_index = Some(index);
181        self
182    }
183
184    /// Set the message type name.
185    #[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    /// Set the sequence number.
192    #[must_use]
193    pub fn sequence(mut self, seq: u64) -> Self {
194        self.sequence = seq;
195        self
196    }
197
198    /// Set an explicit timestamp (nanoseconds since epoch).
199    ///
200    /// For deterministic simulation, use controlled timestamps (or leave unset
201    /// to default to 0). For production contexts with real timestamps, use
202    /// `SystemClock::timestamp_ns()` from the utility layer.
203    #[must_use]
204    pub fn timestamp(mut self, timestamp_ns: u64) -> Self {
205        self.timestamp_ns = Some(timestamp_ns);
206        self
207    }
208
209    /// Set timestamp from an injected wall-clock source.
210    ///
211    /// Use this instead of reading host time directly to keep determinism under
212    /// explicit control in simulations/replays.
213    #[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    /// Set the correlation ID.
220    #[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    /// Set the payload bytes directly.
227    #[must_use]
228    pub fn payload(mut self, payload: Vec<u8>) -> Self {
229        self.payload = payload;
230        self
231    }
232
233    /// Serialize a message as the payload.
234    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    /// Build the envelope.
241    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        // Default to 0 for deterministic simulation; use timestamp() or timestamp_now()
256        // to set an explicit value.
257        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/// Required fields for protocol envelopes.
275#[derive(Debug, Clone, Copy, PartialEq, Eq)]
276pub enum EnvelopeField {
277    /// Protocol name field.
278    Protocol,
279    /// Sender role field.
280    FromRole,
281    /// Recipient role field.
282    ToRole,
283    /// Message type field.
284    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/// Errors that can occur when working with envelopes.
299#[derive(Debug, thiserror::Error)]
300pub enum EnvelopeError {
301    /// A required field was not set.
302    #[error("Missing required field: {0}")]
303    MissingField(EnvelopeField),
304
305    /// Serialization failed.
306    #[error("Serialization error: {0}")]
307    Serialization(String),
308
309    /// Deserialization failed.
310    #[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}