Skip to main content

rocketmq_model/common/message/
message_batch_v2.rs

1// Copyright 2023 The RocketMQ Rust Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt;
16use std::fmt::Display;
17
18use cheetah_string::CheetahString;
19use rocketmq_error::Result;
20
21use crate::common::message::message_single::Message;
22use crate::common::mix_all;
23
24/// Batch message metadata
25#[derive(Clone, Debug)]
26struct BatchMetadata {
27    /// Shared topic for all messages
28    topic: CheetahString,
29
30    /// Whether to wait for storage completion
31    wait_store_msg_ok: bool,
32}
33
34/// Batch message container
35///
36/// Used for sending multiple messages in batch. Requires all messages to have the same topic and
37/// send properties.
38///
39/// # Constraints
40///
41/// - All messages must have the same topic
42/// - All messages must have the same `wait_store_msg_ok` property
43/// - Delayed messages are not supported
44/// - Retry topics are not supported
45///
46/// # Examples
47///
48/// ```rust
49/// use rocketmq_model::common::message::message_batch_v2::MessageBatchV2;
50/// use rocketmq_model::common::message::message_single::Message;
51///
52/// let msg1 = Message::builder()
53///     .topic("TopicTest")
54///     .body_slice(b"Hello")
55///     .build_unchecked();
56/// let msg2 = Message::builder()
57///     .topic("TopicTest")
58///     .body_slice(b"World")
59///     .build_unchecked();
60///
61/// let batch = MessageBatchV2::new(vec![msg1, msg2]).unwrap();
62/// assert_eq!(batch.len(), 2);
63/// assert_eq!(batch.topic().as_str(), "TopicTest");
64/// ```
65#[derive(Clone, Debug)]
66pub struct MessageBatchV2 {
67    /// Batch message metadata
68    metadata: BatchMetadata,
69
70    /// Batch message list
71    messages: Vec<Message>,
72}
73
74impl MessageBatchV2 {
75    #[inline]
76    fn has_delay_property(message: &Message) -> bool {
77        message.delay_time_level() > 0
78            || message.properties().delay_time_ms().unwrap_or(0) > 0
79            || message.properties().delay_time_sec().unwrap_or(0) > 0
80            || message.properties().deliver_time_ms().unwrap_or(0) > 0
81    }
82
83    /// Creates a batch message
84    ///
85    /// # Errors
86    ///
87    /// - Message list is empty
88    /// - Messages have different topics
89    /// - Contains delayed messages
90    /// - Contains retry topics
91    /// - `wait_store_msg_ok` properties are inconsistent
92    pub fn new(messages: Vec<Message>) -> Result<Self> {
93        if messages.is_empty() {
94            return Err(crate::error::invalid_argument("MessageBatch cannot be empty"));
95        }
96
97        // Validate all messages
98        let first = &messages[0];
99        Self::validate_batch_messages(&messages, first)?;
100
101        let metadata = BatchMetadata {
102            topic: first.topic().clone(),
103            wait_store_msg_ok: first.is_wait_store_msg_ok(),
104        };
105
106        Ok(Self { metadata, messages })
107    }
108
109    /// Validates batch message consistency
110    fn validate_batch_messages(messages: &[Message], first: &Message) -> Result<()> {
111        for message in messages {
112            // Delayed messages not supported
113            if Self::has_delay_property(message) {
114                return Err(crate::error::invalid_argument(
115                    "Delayed messages are not supported for batching",
116                ));
117            }
118
119            // Retry topics not supported
120            if message.topic().starts_with(mix_all::RETRY_GROUP_TOPIC_PREFIX) {
121                return Err(crate::error::invalid_argument(
122                    "Retry group topic is not supported for batching",
123                ));
124            }
125
126            // Topics must be consistent
127            if first.topic() != message.topic() {
128                return Err(crate::error::invalid_argument(
129                    "The topic of the messages in one batch should be the same",
130                ));
131            }
132
133            // wait_store_msg_ok must be consistent
134            if first.is_wait_store_msg_ok() != message.is_wait_store_msg_ok() {
135                return Err(crate::error::invalid_argument(
136                    "The waitStoreMsgOK of the messages in one batch should be the same",
137                ));
138            }
139        }
140        Ok(())
141    }
142
143    /// Gets the batch message topic
144    #[inline]
145    pub fn topic(&self) -> &CheetahString {
146        &self.metadata.topic
147    }
148
149    /// Whether to wait for storage completion
150    #[inline]
151    pub fn wait_store_msg_ok(&self) -> bool {
152        self.metadata.wait_store_msg_ok
153    }
154
155    /// Gets the number of batch messages
156    #[inline]
157    pub fn len(&self) -> usize {
158        self.messages.len()
159    }
160
161    /// Checks if the batch is empty
162    #[inline]
163    pub fn is_empty(&self) -> bool {
164        self.messages.is_empty()
165    }
166
167    /// Iterates over all messages
168    #[inline]
169    pub fn iter(&self) -> impl Iterator<Item = &Message> {
170        self.messages.iter()
171    }
172
173    /// Gets a reference to the message list
174    #[inline]
175    pub fn messages(&self) -> &[Message] {
176        &self.messages
177    }
178
179    /// Consumes the batch and returns the internal message list
180    #[inline]
181    pub fn into_messages(self) -> Vec<Message> {
182        self.messages
183    }
184}
185
186impl IntoIterator for MessageBatchV2 {
187    type IntoIter = std::vec::IntoIter<Message>;
188    type Item = Message;
189
190    fn into_iter(self) -> Self::IntoIter {
191        self.messages.into_iter()
192    }
193}
194
195impl<'a> IntoIterator for &'a MessageBatchV2 {
196    type IntoIter = std::slice::Iter<'a, Message>;
197    type Item = &'a Message;
198
199    fn into_iter(self) -> Self::IntoIter {
200        self.messages.iter()
201    }
202}
203
204impl Display for MessageBatchV2 {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        write!(
207            f,
208            "MessageBatchV2 {{ topic: {}, count: {}, wait_store_msg_ok: {} }}",
209            self.metadata.topic,
210            self.messages.len(),
211            self.metadata.wait_store_msg_ok
212        )
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    use crate::common::message::MessageTrait;
221
222    fn create_test_message(topic: &str) -> Message {
223        Message::builder()
224            .topic(topic)
225            .body_slice(b"test body")
226            .build_unchecked()
227    }
228
229    #[test]
230    fn test_batch_creation_ok() {
231        let messages = vec![create_test_message("topic1"), create_test_message("topic1")];
232
233        let batch = MessageBatchV2::new(messages).unwrap();
234        assert_eq!(batch.len(), 2);
235        assert_eq!(batch.topic().as_str(), "topic1");
236        assert!(!batch.is_empty());
237    }
238
239    #[test]
240    fn test_batch_empty_error() {
241        let messages: Vec<Message> = vec![];
242        let result = MessageBatchV2::new(messages);
243        assert!(result.is_err());
244        let err = result.unwrap_err();
245        assert_eq!(err.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
246    }
247
248    #[test]
249    fn test_batch_different_topic_error() {
250        let messages = vec![
251            create_test_message("topic1"),
252            create_test_message("topic2"), // Different topic
253        ];
254
255        let result = MessageBatchV2::new(messages);
256        assert!(result.is_err());
257        let err = result.unwrap_err();
258        assert_eq!(err.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
259    }
260
261    #[test]
262    fn test_batch_delay_message_error() {
263        let msg1 = create_test_message("topic1");
264        let mut msg2 = create_test_message("topic1");
265        msg2.set_delay_time_level(1); // Set delay level
266
267        let messages = vec![msg1, msg2];
268        let result = MessageBatchV2::new(messages);
269        assert!(result.is_err());
270        let err = result.unwrap_err();
271        assert_eq!(err.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
272    }
273
274    #[test]
275    fn test_batch_delay_millis_message_error_matches_java() {
276        let msg1 = create_test_message("topic1");
277        let msg2 = Message::builder()
278            .topic("topic1")
279            .body_slice(b"test body")
280            .delay_millis(1000)
281            .build_unchecked();
282
283        let result = MessageBatchV2::new(vec![msg1, msg2]);
284        assert!(result.is_err());
285        let err = result.unwrap_err();
286        assert_eq!(err.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
287    }
288
289    #[test]
290    fn test_batch_delay_secs_message_error_matches_java() {
291        let msg1 = create_test_message("topic1");
292        let msg2 = Message::builder()
293            .topic("topic1")
294            .body_slice(b"test body")
295            .delay_secs(1)
296            .build_unchecked();
297
298        let result = MessageBatchV2::new(vec![msg1, msg2]);
299        assert!(result.is_err());
300        let err = result.unwrap_err();
301        assert_eq!(err.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
302    }
303
304    #[test]
305    fn test_batch_deliver_time_message_error_matches_java() {
306        let msg1 = create_test_message("topic1");
307        let msg2 = Message::builder()
308            .topic("topic1")
309            .body_slice(b"test body")
310            .deliver_time_ms(1000)
311            .build_unchecked();
312
313        let result = MessageBatchV2::new(vec![msg1, msg2]);
314        assert!(result.is_err());
315        let err = result.unwrap_err();
316        assert_eq!(err.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
317    }
318
319    #[test]
320    fn test_batch_retry_group_error() {
321        let messages = vec![
322            create_test_message("topic1"),
323            create_test_message("%RETRY%topic1"), // Retry group
324        ];
325
326        let result = MessageBatchV2::new(messages);
327        assert!(result.is_err());
328        let err = result.unwrap_err();
329        assert_eq!(err.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
330    }
331
332    #[test]
333    fn test_batch_different_wait_store_msg_ok_error() {
334        let mut msg1 = create_test_message("topic1");
335        let mut msg2 = create_test_message("topic1");
336        msg1.set_wait_store_msg_ok(true);
337        msg2.set_wait_store_msg_ok(false); // Different waitStoreMsgOK
338
339        let messages = vec![msg1, msg2];
340        let result = MessageBatchV2::new(messages);
341        assert!(result.is_err());
342        let err = result.unwrap_err();
343        assert_eq!(err.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
344    }
345
346    #[test]
347    fn test_batch_iterator_slice() {
348        let messages = vec![
349            create_test_message("topic1"),
350            create_test_message("topic1"),
351            create_test_message("topic1"),
352        ];
353
354        let batch = MessageBatchV2::new(messages).unwrap();
355        let count = batch.iter().count();
356        assert_eq!(count, 3);
357    }
358
359    #[test]
360    fn test_batch_into_iterator() {
361        let messages = vec![
362            create_test_message("topic1"),
363            create_test_message("topic1"),
364            create_test_message("topic1"),
365        ];
366
367        let batch = MessageBatchV2::new(messages).unwrap();
368        let count = batch.into_iter().count();
369        assert_eq!(count, 3);
370    }
371
372    #[test]
373    fn test_batch_into_iterator_ref() {
374        let messages = vec![
375            create_test_message("topic1"),
376            create_test_message("topic1"),
377            create_test_message("topic1"),
378        ];
379
380        let batch = MessageBatchV2::new(messages).unwrap();
381        let count = (&batch).into_iter().count();
382        assert_eq!(count, 3);
383
384        // Can still use batch after borrowing
385        assert_eq!(batch.len(), 3);
386    }
387
388    #[test]
389    fn test_batch_properties_inherited() {
390        let mut msg1 = create_test_message("topic1");
391        msg1.set_wait_store_msg_ok(true);
392
393        let messages = vec![msg1.clone(), msg1];
394        let batch = MessageBatchV2::new(messages).unwrap();
395
396        assert_eq!(batch.topic().as_str(), "topic1");
397        assert!(batch.wait_store_msg_ok());
398    }
399
400    #[test]
401    fn test_batch_display_format() {
402        let messages = vec![create_test_message("topic1"), create_test_message("topic1")];
403
404        let batch = MessageBatchV2::new(messages).unwrap();
405        let display = format!("{}", batch);
406        assert!(display.contains("MessageBatchV2"));
407        assert!(display.contains("topic1"));
408        assert!(display.contains("count: 2"));
409    }
410
411    #[test]
412    fn test_batch_into_messages() {
413        let messages = vec![create_test_message("topic1"), create_test_message("topic1")];
414
415        let batch = MessageBatchV2::new(messages).unwrap();
416        let extracted = batch.into_messages();
417        assert_eq!(extracted.len(), 2);
418    }
419}