rocketmq_model/common/message/
message_batch_v2.rs1use 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#[derive(Clone, Debug)]
26struct BatchMetadata {
27 topic: CheetahString,
29
30 wait_store_msg_ok: bool,
32}
33
34#[derive(Clone, Debug)]
66pub struct MessageBatchV2 {
67 metadata: BatchMetadata,
69
70 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 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 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 fn validate_batch_messages(messages: &[Message], first: &Message) -> Result<()> {
111 for message in messages {
112 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 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 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 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 #[inline]
145 pub fn topic(&self) -> &CheetahString {
146 &self.metadata.topic
147 }
148
149 #[inline]
151 pub fn wait_store_msg_ok(&self) -> bool {
152 self.metadata.wait_store_msg_ok
153 }
154
155 #[inline]
157 pub fn len(&self) -> usize {
158 self.messages.len()
159 }
160
161 #[inline]
163 pub fn is_empty(&self) -> bool {
164 self.messages.is_empty()
165 }
166
167 #[inline]
169 pub fn iter(&self) -> impl Iterator<Item = &Message> {
170 self.messages.iter()
171 }
172
173 #[inline]
175 pub fn messages(&self) -> &[Message] {
176 &self.messages
177 }
178
179 #[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"), ];
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); 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"), ];
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); 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 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}