Skip to main content

monocoque_core/
message_builder.rs

1//! Ergonomic message builder for constructing `ZeroMQ` multipart messages.
2//!
3//! This module provides a fluent API for building multipart messages with
4//! automatic frame handling and type conversions.
5
6use bytes::Bytes;
7
8/// Builder for constructing `ZeroMQ` multipart messages.
9///
10/// Provides a fluent API for adding frames to a message with automatic
11/// conversions from common types (strings, bytes, JSON, etc.).
12///
13/// # Examples
14///
15/// ```
16/// use monocoque_core::message_builder::Message;
17///
18/// // Simple text frames
19/// let msg = Message::new()
20///     .push_str("topic")
21///     .push_str("Hello, World!")
22///     .into_frames();
23///
24/// // Mixed types
25/// let msg = Message::new()
26///     .push(&b"routing_id"[..])
27///     .push_str("command:execute")
28///     .push(&[1u8, 2, 3, 4][..])
29///     .into_frames();
30/// ```
31///
32/// ## With Serde JSON (optional)
33///
34/// ```ignore
35/// #[derive(Serialize)]
36/// struct Task {
37///     id: u64,
38///     name: String,
39/// }
40///
41/// let task = Task { id: 42, name: "Process data".to_string() };
42/// let msg = Message::new()
43///     .push_str("tasks")
44///     .push_json(&task)?
45///     .into_frames();
46/// ```
47#[derive(Debug, Clone, Default)]
48pub struct Message {
49    frames: Vec<Bytes>,
50}
51
52impl Message {
53    /// Create a new empty message builder.
54    ///
55    /// # Examples
56    ///
57    /// ```
58    /// use monocoque_core::message_builder::Message;
59    ///
60    /// let msg = Message::new();
61    /// ```
62    #[must_use]
63    pub const fn new() -> Self {
64        Self { frames: Vec::new() }
65    }
66
67    /// Create a message with pre-allocated capacity.
68    ///
69    /// Useful when you know the number of frames in advance to avoid reallocations.
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// use monocoque_core::message_builder::Message;
75    ///
76    /// let msg = Message::with_capacity(4)
77    ///     .push_str("frame1")
78    ///     .push_str("frame2")
79    ///     .push_str("frame3")
80    ///     .push_str("frame4");
81    /// ```
82    #[must_use]
83    pub fn with_capacity(capacity: usize) -> Self {
84        Self {
85            frames: Vec::with_capacity(capacity),
86        }
87    }
88
89    /// Add a frame from any type that can be converted to `Bytes`.
90    ///
91    /// # Examples
92    ///
93    /// ```
94    /// use monocoque_core::message_builder::Message;
95    /// use bytes::Bytes;
96    ///
97    /// let msg = Message::new()
98    ///     .push(&b"raw bytes"[..])
99    ///     .push(vec![1, 2, 3])
100    ///     .push(Bytes::from_static(b"static"));
101    /// ```
102    pub fn push(mut self, frame: impl Into<Bytes>) -> Self {
103        self.frames.push(frame.into());
104        self
105    }
106
107    /// Add a string frame.
108    ///
109    /// Convenience method for adding UTF-8 strings.
110    ///
111    /// # Examples
112    ///
113    /// ```
114    /// use monocoque_core::message_builder::Message;
115    ///
116    /// let msg = Message::new()
117    ///     .push_str("Hello")
118    ///     .push_str("World");
119    /// ```
120    #[must_use]
121    pub fn push_str(mut self, s: &str) -> Self {
122        self.frames.push(Bytes::copy_from_slice(s.as_bytes()));
123        self
124    }
125
126    /// Add an empty frame.
127    ///
128    /// Empty frames are often used as delimiters in `ZeroMQ` envelope patterns.
129    ///
130    /// # Examples
131    ///
132    /// ```
133    /// use monocoque_core::message_builder::Message;
134    ///
135    /// // ROUTER envelope: [identity, empty, body]
136    /// let msg = Message::new()
137    ///     .push(&b"client-123"[..])
138    ///     .push_empty()
139    ///     .push_str("Hello");
140    /// ```
141    #[must_use]
142    pub fn push_empty(mut self) -> Self {
143        self.frames.push(Bytes::new());
144        self
145    }
146
147    /// Add a frame containing JSON-serialized data.
148    ///
149    /// Requires the `serde` feature to be enabled.
150    ///
151    /// # Errors
152    ///
153    /// Returns an error if serialization fails.
154    ///
155    /// # Examples
156    ///
157    /// ```ignore
158    /// use monocoque_core::message_builder::Message;
159    /// use serde::Serialize;
160    ///
161    /// #[derive(Serialize)]
162    /// struct Data {
163    ///     value: i32,
164    /// }
165    ///
166    /// let data = Data { value: 42 };
167    /// let msg = Message::new()
168    ///     .push_str("data")
169    ///     .push_json(&data)?
170    ///     .into_frames();
171    /// # Ok::<(), Box<dyn std::error::Error>>(())
172    /// ```
173    #[cfg(feature = "serde")]
174    pub fn push_json<T: serde::Serialize>(mut self, value: &T) -> Result<Self, serde_json::Error> {
175        let json = serde_json::to_vec(value)?;
176        self.frames.push(Bytes::from(json));
177        Ok(self)
178    }
179
180    /// Add a frame containing MessagePack-serialized data.
181    ///
182    /// Requires the `msgpack` feature to be enabled. MessagePack is more compact
183    /// than JSON and often preferred for binary protocols.
184    ///
185    /// # Errors
186    ///
187    /// Returns an error if serialization fails.
188    #[cfg(feature = "msgpack")]
189    pub fn push_msgpack<T: serde::Serialize>(
190        mut self,
191        value: &T,
192    ) -> Result<Self, rmp_serde::encode::Error> {
193        let msgpack = rmp_serde::to_vec(value)?;
194        self.frames.push(Bytes::from(msgpack));
195        Ok(self)
196    }
197
198    /// Add a frame containing a big-endian u32.
199    ///
200    /// Useful for protocol headers, message IDs, etc.
201    ///
202    /// # Examples
203    ///
204    /// ```
205    /// use monocoque_core::message_builder::Message;
206    ///
207    /// let msg = Message::new()
208    ///     .push_u32(12345) // Message ID
209    ///     .push_str("payload");
210    /// ```
211    #[must_use]
212    pub fn push_u32(mut self, value: u32) -> Self {
213        self.frames
214            .push(Bytes::copy_from_slice(&value.to_be_bytes()));
215        self
216    }
217
218    /// Add a frame containing a big-endian u64.
219    #[must_use]
220    pub fn push_u64(mut self, value: u64) -> Self {
221        self.frames
222            .push(Bytes::copy_from_slice(&value.to_be_bytes()));
223        self
224    }
225
226    /// Get the number of frames in the message.
227    ///
228    /// # Examples
229    ///
230    /// ```
231    /// use monocoque_core::message_builder::Message;
232    ///
233    /// let msg = Message::new()
234    ///     .push_str("frame1")
235    ///     .push_str("frame2");
236    ///
237    /// assert_eq!(msg.len(), 2);
238    /// ```
239    #[must_use]
240    pub fn len(&self) -> usize {
241        self.frames.len()
242    }
243
244    /// Check if the message is empty (has no frames).
245    #[must_use]
246    pub fn is_empty(&self) -> bool {
247        self.frames.is_empty()
248    }
249
250    /// Consume the builder and return the frames as a `Vec<Bytes>`.
251    ///
252    /// This is the final step to get the message ready for sending.
253    ///
254    /// # Examples
255    ///
256    /// ```
257    /// use monocoque_core::message_builder::Message;
258    ///
259    /// let frames = Message::new()
260    ///     .push_str("Hello")
261    ///     .push_str("World")
262    ///     .into_frames();
263    /// assert_eq!(frames.len(), 2);
264    /// ```
265    #[must_use]
266    pub fn into_frames(self) -> Vec<Bytes> {
267        self.frames
268    }
269
270    /// Get a reference to the frames without consuming the builder.
271    #[must_use]
272    pub fn frames(&self) -> &[Bytes] {
273        &self.frames
274    }
275
276    /// Create a message from existing frames.
277    ///
278    /// # Examples
279    ///
280    /// ```
281    /// use monocoque_core::message_builder::Message;
282    /// use bytes::Bytes;
283    ///
284    /// let frames = vec![
285    ///     Bytes::from_static(b"frame1"),
286    ///     Bytes::from_static(b"frame2"),
287    /// ];
288    ///
289    /// let msg = Message::from_frames(frames);
290    /// assert_eq!(msg.len(), 2);
291    /// ```
292    #[must_use]
293    pub const fn from_frames(frames: Vec<Bytes>) -> Self {
294        Self { frames }
295    }
296}
297
298impl From<Vec<Bytes>> for Message {
299    fn from(frames: Vec<Bytes>) -> Self {
300        Self::from_frames(frames)
301    }
302}
303
304impl From<Message> for Vec<Bytes> {
305    fn from(msg: Message) -> Self {
306        msg.into_frames()
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    #[test]
315    fn test_empty_message() {
316        let msg = Message::new();
317        assert_eq!(msg.len(), 0);
318        assert!(msg.is_empty());
319    }
320
321    #[test]
322    fn test_build_message() {
323        let msg = Message::new()
324            .push_str("topic")
325            .push_str("Hello")
326            .push(Vec::from(&b"World"[..]));
327
328        assert_eq!(msg.len(), 3);
329        assert!(!msg.is_empty());
330
331        let frames = msg.into_frames();
332        assert_eq!(frames[0], Bytes::from_static(b"topic"));
333        assert_eq!(frames[1], Bytes::from_static(b"Hello"));
334        assert_eq!(frames[2], Bytes::from_static(b"World"));
335    }
336
337    #[test]
338    fn test_push_empty() {
339        let msg = Message::new()
340            .push(Vec::from(&b"id"[..]))
341            .push_empty()
342            .push_str("body");
343
344        let frames = msg.into_frames();
345        assert_eq!(frames.len(), 3);
346        assert_eq!(frames[1].len(), 0);
347    }
348
349    #[test]
350    fn test_push_integers() {
351        let msg = Message::new().push_u32(12345).push_u64(67890);
352
353        let frames = msg.into_frames();
354        assert_eq!(frames[0].len(), 4);
355        assert_eq!(frames[1].len(), 8);
356
357        let val32 = u32::from_be_bytes(frames[0].as_ref().try_into().unwrap());
358        assert_eq!(val32, 12345);
359
360        let val64 = u64::from_be_bytes(frames[1].as_ref().try_into().unwrap());
361        assert_eq!(val64, 67890);
362    }
363
364    #[test]
365    fn test_with_capacity() {
366        let msg = Message::with_capacity(10);
367        assert_eq!(msg.len(), 0);
368        assert!(msg.frames.capacity() >= 10);
369    }
370
371    #[test]
372    fn test_from_frames() {
373        let frames = vec![Bytes::from_static(b"a"), Bytes::from_static(b"b")];
374        let msg = Message::from_frames(frames.clone());
375        assert_eq!(msg.len(), 2);
376        assert_eq!(msg.frames(), &frames[..]);
377    }
378}