Skip to main content

rocketmq_common/common/message/
message_batch.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::any::Any;
16use std::collections::HashMap;
17use std::fmt;
18use std::fmt::Debug;
19use std::fmt::Display;
20use std::fmt::Formatter;
21
22use bytes::Bytes;
23use cheetah_string::CheetahString;
24// Use new unified error system
25use rocketmq_error::RocketMQError;
26
27use crate::common::message::message_decoder;
28use crate::common::message::message_ext_broker_inner::MessageExtBrokerInner;
29use crate::common::message::message_property::MessageProperties;
30use crate::common::message::message_single::Message;
31use crate::common::message::MessageTrait;
32use crate::common::mix_all;
33
34#[derive(Clone, Default, Debug)]
35pub struct MessageBatch {
36    ///`final_message` stores the batch-encoded messages.
37    pub final_message: Message,
38
39    ///`messages` stores the batch of initialized messages.
40    pub messages: Vec<Message>,
41}
42
43impl MessageBatch {
44    #[inline]
45    fn has_delay_property(message: &Message) -> bool {
46        message.delay_time_level() > 0
47            || message.properties().delay_time_ms().unwrap_or(0) > 0
48            || message.properties().delay_time_sec().unwrap_or(0) > 0
49            || message.properties().deliver_time_ms().unwrap_or(0) > 0
50    }
51
52    /// Encode all messages in the batch.
53    #[inline]
54    pub fn encode(&self) -> Bytes {
55        message_decoder::encode_messages(&self.messages)
56    }
57
58    /// Get an iterator over the messages in the batch.
59    #[inline]
60    pub fn iter(&self) -> std::slice::Iter<'_, Message> {
61        self.messages.iter()
62    }
63
64    /// Get the number of messages in the batch.
65    #[inline]
66    pub fn len(&self) -> usize {
67        self.messages.len()
68    }
69
70    /// Check if the batch is empty.
71    #[inline]
72    pub fn is_empty(&self) -> bool {
73        self.messages.is_empty()
74    }
75
76    pub fn generate_from_vec<M>(messages: Vec<M>) -> rocketmq_error::RocketMQResult<MessageBatch>
77    where
78        M: MessageTrait,
79    {
80        if messages.is_empty() {
81            return Err(RocketMQError::illegal_argument(
82                "MessageBatch::generate_from_vec: messages is empty",
83            ));
84        }
85
86        let mut message_list = Vec::with_capacity(messages.len());
87        for msg in messages {
88            if let Some(m) = msg.as_any().downcast_ref::<Message>() {
89                message_list.push(m.clone());
90            } else {
91                let mut m = Message::default();
92                m.set_topic(msg.topic().clone());
93                if let Some(body) = msg.get_body() {
94                    m.set_body(Some(body.clone()));
95                }
96                m.set_flag(msg.get_flag());
97                if let Some(transaction_id) = msg.transaction_id() {
98                    m.set_transaction_id(transaction_id.clone());
99                }
100                m.set_properties(msg.get_properties().clone());
101                message_list.push(m);
102            }
103        }
104
105        Self::generate_from_messages(message_list)
106    }
107
108    /// Build a batch from owned [`Message`] values without cloning each message again.
109    ///
110    /// This keeps the same Java-compatible validation as [`Self::generate_from_vec`], but it is
111    /// the preferred hot path when callers already normalized inputs to concrete `Message`s.
112    pub fn generate_from_messages(messages: Vec<Message>) -> rocketmq_error::RocketMQResult<MessageBatch> {
113        if messages.is_empty() {
114            return Err(RocketMQError::illegal_argument(
115                "MessageBatch::generate_from_vec: messages is empty",
116            ));
117        }
118
119        let mut first: Option<&Message> = None;
120        for message in &messages {
121            if Self::has_delay_property(message) {
122                return Err(RocketMQError::illegal_argument(
123                    "Delayed messages are not supported for batching",
124                ));
125            }
126            if message.topic().starts_with(mix_all::RETRY_GROUP_TOPIC_PREFIX) {
127                return Err(RocketMQError::illegal_argument(
128                    "Retry group topic is not supported for batching",
129                ));
130            }
131
132            if let Some(first_message) = first {
133                if first_message.topic() != message.topic() {
134                    return Err(RocketMQError::illegal_argument(
135                        "The topic of the messages in one batch should be the same",
136                    ));
137                }
138                if first_message.is_wait_store_msg_ok() != message.is_wait_store_msg_ok() {
139                    return Err(RocketMQError::illegal_argument(
140                        "The waitStoreMsgOK of the messages in one batch should the same",
141                    ));
142                }
143            } else {
144                first = Some(message);
145            }
146        }
147        let Some(first) = first else {
148            return Err(RocketMQError::illegal_argument(
149                "MessageBatch::generate_from_vec: messages is empty",
150            ));
151        };
152        let mut final_message = Message::default();
153        final_message.set_topic(first.topic().clone());
154        final_message.set_wait_store_msg_ok(first.is_wait_store_msg_ok());
155        Ok(MessageBatch {
156            final_message,
157            messages,
158        })
159    }
160}
161
162impl IntoIterator for MessageBatch {
163    type Item = Message;
164    type IntoIter = std::vec::IntoIter<Message>;
165
166    fn into_iter(self) -> Self::IntoIter {
167        self.messages.into_iter()
168    }
169}
170
171impl<'a> IntoIterator for &'a MessageBatch {
172    type Item = &'a Message;
173    type IntoIter = std::slice::Iter<'a, Message>;
174
175    fn into_iter(self) -> Self::IntoIter {
176        self.messages.iter()
177    }
178}
179
180impl fmt::Display for MessageBatch {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        let messages_str = self
183            .messages
184            .iter()
185            .map(|msg| msg.to_string())
186            .collect::<Vec<_>>()
187            .join(", ");
188
189        write!(
190            f,
191            "MessageBatch {{ final_message: {}, messages: {} }}",
192            self.final_message, messages_str
193        )
194    }
195}
196
197#[allow(unused_variables)]
198impl MessageTrait for MessageBatch {
199    #[inline]
200    fn put_property(&mut self, key: CheetahString, value: CheetahString) {
201        self.final_message.properties_mut().as_map_mut().insert(key, value);
202    }
203
204    #[inline]
205    fn clear_property(&mut self, name: &str) {
206        self.final_message.properties_mut().as_map_mut().remove(name);
207    }
208
209    #[inline]
210    fn property(&self, name: &CheetahString) -> Option<CheetahString> {
211        self.final_message.properties().as_map().get(name).cloned()
212    }
213
214    fn property_ref(&self, name: &CheetahString) -> Option<&CheetahString> {
215        self.final_message.properties().as_map().get(name)
216    }
217
218    #[inline]
219    fn topic(&self) -> &CheetahString {
220        self.final_message.topic()
221    }
222
223    #[inline]
224    fn set_topic(&mut self, topic: CheetahString) {
225        self.final_message.set_topic(topic);
226    }
227
228    #[inline]
229    fn get_flag(&self) -> i32 {
230        self.final_message.flag()
231    }
232
233    #[inline]
234    fn set_flag(&mut self, flag: i32) {
235        self.final_message.set_flag(flag);
236    }
237
238    #[inline]
239    fn get_body(&self) -> Option<&Bytes> {
240        self.final_message.get_body()
241    }
242
243    #[inline]
244    fn set_body(&mut self, body: Bytes) {
245        self.final_message.set_body(Some(body));
246    }
247
248    #[inline]
249    fn get_properties(&self) -> &HashMap<CheetahString, CheetahString> {
250        self.final_message.properties().as_map()
251    }
252
253    #[inline]
254    fn set_properties(&mut self, properties: HashMap<CheetahString, CheetahString>) {
255        *self.final_message.properties_mut() = MessageProperties::from_map(properties);
256    }
257
258    #[inline]
259    fn transaction_id(&self) -> Option<&CheetahString> {
260        MessageTrait::transaction_id(&self.final_message)
261    }
262
263    #[inline]
264    fn set_transaction_id(&mut self, transaction_id: CheetahString) {
265        *self.final_message.transaction_id_mut() = Some(transaction_id);
266    }
267
268    #[inline]
269    fn get_compressed_body_mut(&mut self) -> Option<&mut Bytes> {
270        self.final_message.body_mut().compressed_mut().as_mut()
271    }
272
273    #[inline]
274    fn get_compressed_body(&self) -> Option<&Bytes> {
275        self.final_message.compressed_body()
276    }
277
278    #[inline]
279    fn set_compressed_body_mut(&mut self, compressed_body: Bytes) {
280        self.final_message.set_compressed_body_mut(compressed_body);
281    }
282
283    #[inline]
284    fn take_body(&mut self) -> Option<Bytes> {
285        self.final_message.take_body()
286    }
287
288    fn as_any(&self) -> &dyn Any {
289        self
290    }
291
292    fn as_any_mut(&mut self) -> &mut dyn Any {
293        self
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    use bytes::Bytes;
300    use cheetah_string::CheetahString;
301
302    use super::*;
303
304    fn create_test_message(topic: &str) -> Message {
305        let mut msg = Message::default();
306        msg.set_topic(CheetahString::from_string(topic.to_string()));
307        msg.set_body(Some(Bytes::from_static(b"test body")));
308        msg
309    }
310
311    #[test]
312    fn test_generate_from_vec_ok() {
313        let messages = vec![create_test_message("topic1"), create_test_message("topic1")];
314        let result = MessageBatch::generate_from_vec(messages);
315        assert!(result.is_ok());
316        let batch = result.unwrap();
317        assert_eq!(batch.messages.len(), 2);
318        assert_eq!(batch.len(), 2);
319        assert!(!batch.is_empty());
320    }
321
322    #[test]
323    fn test_generate_from_messages_ok() {
324        let messages = vec![create_test_message("topic1"), create_test_message("topic1")];
325        let batch = MessageBatch::generate_from_messages(messages).unwrap();
326
327        assert_eq!(batch.messages.len(), 2);
328        assert_eq!(batch.final_message.topic().as_str(), "topic1");
329    }
330
331    #[test]
332    fn test_generate_from_vec_empty() {
333        let messages: Vec<Message> = vec![];
334        let result = MessageBatch::generate_from_vec(messages);
335        assert!(result.is_err());
336        let err = result.unwrap_err();
337        assert!(err.to_string().contains("empty"));
338    }
339
340    #[test]
341    fn test_generate_from_vec_different_topic() {
342        let messages = vec![
343            create_test_message("topic1"),
344            create_test_message("topic2"), // Different topic
345        ];
346        let result = MessageBatch::generate_from_vec(messages);
347        assert!(result.is_err());
348        let err = result.unwrap_err();
349        assert!(err.to_string().contains("topic"));
350    }
351
352    #[test]
353    fn test_generate_from_vec_delay_message() {
354        let msg1 = create_test_message("topic1");
355        let mut msg2 = create_test_message("topic1");
356        msg2.set_delay_time_level(1); // Set delay level
357
358        let messages = vec![msg1, msg2];
359        let result = MessageBatch::generate_from_vec(messages);
360        assert!(result.is_err());
361        let err = result.unwrap_err();
362        assert!(err.to_string().contains("Delayed messages"));
363    }
364
365    #[test]
366    fn test_generate_from_vec_delay_millis_message_matches_java_rejection() {
367        let msg1 = create_test_message("topic1");
368        let msg2 = Message::builder()
369            .topic("topic1")
370            .body(Bytes::from_static(b"test body"))
371            .delay_millis(1000)
372            .build_unchecked();
373
374        let result = MessageBatch::generate_from_vec(vec![msg1, msg2]);
375        assert!(result.is_err());
376        let err = result.unwrap_err();
377        assert!(err.to_string().contains("Delayed messages"));
378    }
379
380    #[test]
381    fn test_generate_from_vec_delay_secs_message_matches_java_rejection() {
382        let msg1 = create_test_message("topic1");
383        let msg2 = Message::builder()
384            .topic("topic1")
385            .body(Bytes::from_static(b"test body"))
386            .delay_secs(1)
387            .build_unchecked();
388
389        let result = MessageBatch::generate_from_vec(vec![msg1, msg2]);
390        assert!(result.is_err());
391        let err = result.unwrap_err();
392        assert!(err.to_string().contains("Delayed messages"));
393    }
394
395    #[test]
396    fn test_generate_from_vec_deliver_time_message_matches_java_rejection() {
397        let msg1 = create_test_message("topic1");
398        let msg2 = Message::builder()
399            .topic("topic1")
400            .body(Bytes::from_static(b"test body"))
401            .deliver_time_ms(1000)
402            .build_unchecked();
403
404        let result = MessageBatch::generate_from_vec(vec![msg1, msg2]);
405        assert!(result.is_err());
406        let err = result.unwrap_err();
407        assert!(err.to_string().contains("Delayed messages"));
408    }
409
410    #[test]
411    fn test_generate_from_vec_retry_group() {
412        let messages = vec![
413            create_test_message("topic1"),
414            create_test_message("%RETRY%topic1"), // Retry group
415        ];
416        let result = MessageBatch::generate_from_vec(messages);
417        assert!(result.is_err());
418        let err = result.unwrap_err();
419        assert!(err.to_string().contains("Retry"));
420    }
421
422    #[test]
423    fn test_generate_from_vec_different_wait_store_msg_ok() {
424        let mut msg1 = create_test_message("topic1");
425        let mut msg2 = create_test_message("topic1");
426        msg1.set_wait_store_msg_ok(true);
427        msg2.set_wait_store_msg_ok(false); // Different waitStoreMsgOK
428
429        let messages = vec![msg1, msg2];
430        let result = MessageBatch::generate_from_vec(messages);
431        assert!(result.is_err());
432        let err = result.unwrap_err();
433        assert!(err.to_string().contains("waitStoreMsgOK"));
434    }
435
436    #[test]
437    fn test_encode() {
438        let messages = vec![create_test_message("topic1"), create_test_message("topic1")];
439        let batch = MessageBatch::generate_from_vec(messages).unwrap();
440        let encoded = batch.encode();
441        assert!(!encoded.is_empty());
442    }
443
444    #[test]
445    fn test_iterator_slice() {
446        let messages = vec![
447            create_test_message("topic1"),
448            create_test_message("topic1"),
449            create_test_message("topic1"),
450        ];
451        let batch = MessageBatch::generate_from_vec(messages).unwrap();
452
453        let count = batch.iter().count();
454        assert_eq!(count, 3);
455    }
456
457    #[test]
458    fn test_into_iterator() {
459        let messages = vec![
460            create_test_message("topic1"),
461            create_test_message("topic1"),
462            create_test_message("topic1"),
463        ];
464        let batch = MessageBatch::generate_from_vec(messages).unwrap();
465
466        let count = batch.into_iter().count();
467        assert_eq!(count, 3);
468    }
469
470    #[test]
471    fn test_into_iterator_ref() {
472        let messages = vec![
473            create_test_message("topic1"),
474            create_test_message("topic1"),
475            create_test_message("topic1"),
476        ];
477        let batch = MessageBatch::generate_from_vec(messages).unwrap();
478
479        let count = (&batch).into_iter().count();
480        assert_eq!(count, 3);
481
482        // Can still use batch after borrowing
483        assert_eq!(batch.len(), 3);
484    }
485
486    #[test]
487    fn test_batch_properties_inherited() {
488        let mut msg1 = create_test_message("topic1");
489        msg1.set_wait_store_msg_ok(true);
490
491        let messages = vec![msg1.clone(), msg1];
492        let batch = MessageBatch::generate_from_vec(messages).unwrap();
493
494        assert_eq!(batch.final_message.topic().as_str(), "topic1");
495        assert!(batch.final_message.is_wait_store_msg_ok());
496    }
497
498    #[test]
499    fn test_display_format() {
500        let messages = vec![create_test_message("topic1"), create_test_message("topic1")];
501        let batch = MessageBatch::generate_from_vec(messages).unwrap();
502
503        let display = format!("{}", batch);
504        assert!(display.contains("MessageBatch"));
505        assert!(display.contains("topic1"));
506    }
507}
508
509#[derive(Debug, Default)]
510pub struct MessageExtBatch {
511    pub message_ext_broker_inner: MessageExtBrokerInner,
512    pub is_inner_batch: bool,
513    pub encoded_buff: Option<bytes::BytesMut>,
514}
515
516impl MessageExtBatch {
517    pub fn wrap(&self) -> Option<Bytes> {
518        self.message_ext_broker_inner.body()
519    }
520
521    pub fn get_tags(&self) -> Option<CheetahString> {
522        self.message_ext_broker_inner.get_tags()
523    }
524}