Skip to main content

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