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}