1use std::any::Any;
16use std::collections::HashMap;
17use std::fmt;
18use std::fmt::Display;
19use std::fmt::Formatter;
20use std::hash::DefaultHasher;
21use std::hash::Hash;
22use std::hash::Hasher;
23use std::net::SocketAddr;
24
25use bytes::Buf;
26use bytes::BufMut;
27use bytes::Bytes;
28use cheetah_string::CheetahString;
29
30use crate::common::hasher::string_hasher::JavaStringHasher;
31use crate::common::message::message_body::MessageBody;
32use crate::common::message::message_builder::MessageBuilder;
33use crate::common::message::message_ext::MessageExt;
34use crate::common::message::message_flag::MessageFlag;
35use crate::common::message::message_property::MessageProperties;
36use crate::common::message::message_property::MessagePropertyKey;
37use crate::common::message::MessageConst;
38use crate::common::message::MessageTrait;
39use crate::common::message::MessageVersion;
40use crate::common::sys_flag::message_sys_flag::MessageSysFlag;
41use crate::common::TopicFilterType;
42use crate::MessageUtils;
43
44#[derive(Clone, Debug)]
45pub struct Message {
46 topic: CheetahString,
47 flag: MessageFlag,
48 properties: MessageProperties,
49 body: MessageBody,
50 transaction_id: Option<CheetahString>,
51}
52
53impl Message {
55 pub(crate) fn from_builder(
56 topic: CheetahString,
57 body: MessageBody,
58 properties: MessageProperties,
59 flag: MessageFlag,
60 transaction_id: Option<CheetahString>,
61 ) -> Self {
62 Self {
63 topic,
64 flag,
65 properties,
66 body,
67 transaction_id,
68 }
69 }
70}
71
72impl Default for Message {
73 fn default() -> Self {
74 Self {
75 topic: CheetahString::new(),
76 flag: MessageFlag::empty(),
77 properties: MessageProperties::new(),
78 body: MessageBody::empty(),
79 transaction_id: None,
80 }
81 }
82}
83
84impl Message {
85 pub fn builder() -> MessageBuilder {
101 MessageBuilder::new()
102 }
103
104 #[deprecated(since = "0.8.0", note = "Use Message::builder() instead")]
106 #[allow(deprecated)]
107 #[inline]
108 pub fn new(topic: impl Into<CheetahString>, body: &[u8]) -> Self {
109 Self::with_details(topic, CheetahString::empty(), CheetahString::empty(), 0, body, true)
110 }
111
112 #[deprecated(since = "0.8.0", note = "Use Message::builder() instead")]
114 #[allow(deprecated)]
115 #[inline]
116 pub fn new_with_bytes(topic: impl Into<CheetahString>, body: Bytes) -> Self {
117 Self::with_details_bytes(topic, CheetahString::empty(), CheetahString::empty(), 0, body, true)
118 }
119
120 #[deprecated(since = "0.8.0", note = "Use Message::builder() instead")]
122 #[allow(deprecated)]
123 #[inline]
124 pub fn new_with_vec(topic: impl Into<CheetahString>, body: Vec<u8>) -> Self {
125 Self::with_details_bytes(
126 topic,
127 CheetahString::empty(),
128 CheetahString::empty(),
129 0,
130 Bytes::from(body),
131 true,
132 )
133 }
134
135 #[deprecated(since = "0.8.0", note = "Use Message::builder() instead")]
136 #[allow(deprecated)]
137 #[inline]
138 pub fn new_body(topic: impl Into<CheetahString>, body: Option<Bytes>) -> Self {
139 Self::with_details_body(topic, CheetahString::empty(), CheetahString::empty(), 0, body, true)
140 }
141
142 #[deprecated(since = "0.8.0", note = "Use Message::builder() instead")]
144 #[allow(deprecated)]
145 #[inline]
146 pub fn with_tags(topic: impl Into<CheetahString>, tags: impl Into<CheetahString>, body: &[u8]) -> Self {
147 Self::with_details(topic, tags, CheetahString::empty(), 0, body, true)
148 }
149
150 #[deprecated(since = "0.8.0", note = "Use Message::builder() instead")]
152 #[allow(deprecated)]
153 #[inline]
154 pub fn with_tags_bytes(topic: impl Into<CheetahString>, tags: impl Into<CheetahString>, body: Bytes) -> Self {
155 Self::with_details_bytes(topic, tags, CheetahString::empty(), 0, body, true)
156 }
157
158 #[deprecated(since = "0.8.0", note = "Use Message::builder() instead")]
160 #[allow(deprecated)]
161 #[inline]
162 pub fn with_keys(
163 topic: impl Into<CheetahString>,
164 tags: impl Into<CheetahString>,
165 keys: impl Into<CheetahString>,
166 body: &[u8],
167 ) -> Self {
168 Self::with_details(topic, tags, keys, 0, body, true)
169 }
170
171 #[deprecated(since = "0.8.0", note = "Use Message::builder() instead")]
173 #[allow(deprecated)]
174 #[inline]
175 pub fn with_keys_bytes(
176 topic: impl Into<CheetahString>,
177 tags: impl Into<CheetahString>,
178 keys: impl Into<CheetahString>,
179 body: Bytes,
180 ) -> Self {
181 Self::with_details_bytes(topic, tags, keys, 0, body, true)
182 }
183
184 #[deprecated(since = "0.8.0", note = "Use Message::builder() instead")]
186 #[allow(deprecated)]
187 pub fn with_details(
188 topic: impl Into<CheetahString>,
189 tags: impl Into<CheetahString>,
190 keys: impl Into<CheetahString>,
191 flag: i32,
192 body: &[u8],
193 wait_store_msg_ok: bool,
194 ) -> Self {
195 Self::with_details_bytes(topic, tags, keys, flag, Bytes::copy_from_slice(body), wait_store_msg_ok)
196 }
197
198 #[deprecated(since = "0.8.0", note = "Use Message::builder() instead")]
200 pub fn with_details_bytes(
201 topic: impl Into<CheetahString>,
202 tags: impl Into<CheetahString>,
203 keys: impl Into<CheetahString>,
204 flag: i32,
205 body: Bytes,
206 wait_store_msg_ok: bool,
207 ) -> Self {
208 let topic = topic.into();
209 let tags = tags.into();
210 let keys = keys.into();
211
212 let has_tags = !tags.is_empty();
214 let has_keys = !keys.is_empty();
215 let initial_capacity = (has_tags as usize) + (has_keys as usize) + 1;
216 let mut properties = HashMap::with_capacity(initial_capacity);
217
218 if has_tags {
220 properties.insert(CheetahString::from_static_str(MessageConst::PROPERTY_TAGS), tags);
221 }
222
223 if has_keys {
224 properties.insert(CheetahString::from_static_str(MessageConst::PROPERTY_KEYS), keys);
225 }
226
227 properties.insert(
228 CheetahString::from_static_str(MessageConst::PROPERTY_WAIT_STORE_MSG_OK),
229 CheetahString::from_static_str(if wait_store_msg_ok { "true" } else { "false" }),
230 );
231
232 Message {
233 topic,
234 flag: MessageFlag::from_bits(flag),
235 properties: MessageProperties::from_map(properties),
236 body: MessageBody::from(body),
237 transaction_id: None,
238 }
239 }
240
241 pub fn with_details_body(
242 topic: impl Into<CheetahString>,
243 tags: impl Into<CheetahString>,
244 keys: impl Into<CheetahString>,
245 flag: i32,
246 body: Option<Bytes>,
247 wait_store_msg_ok: bool,
248 ) -> Self {
249 let topic = topic.into();
250 let tags = tags.into();
251 let keys = keys.into();
252
253 let has_tags = !tags.is_empty();
255 let has_keys = !keys.is_empty();
256 let initial_capacity = (has_tags as usize) + (has_keys as usize) + 1;
257 let mut properties = HashMap::with_capacity(initial_capacity);
258
259 if has_tags {
260 properties.insert(CheetahString::from_static_str(MessageConst::PROPERTY_TAGS), tags);
261 }
262
263 if has_keys {
264 properties.insert(CheetahString::from_static_str(MessageConst::PROPERTY_KEYS), keys);
265 }
266
267 properties.insert(
268 CheetahString::from_static_str(MessageConst::PROPERTY_WAIT_STORE_MSG_OK),
269 CheetahString::from_static_str(if wait_store_msg_ok { "true" } else { "false" }),
270 );
271
272 Message {
273 topic,
274 flag: MessageFlag::from_bits(flag),
275 properties: MessageProperties::from_map(properties),
276 body: if let Some(b) = body {
277 MessageBody::from(b)
278 } else {
279 MessageBody::empty()
280 },
281 transaction_id: None,
282 }
283 }
284
285 #[inline]
286 pub fn set_tags(&mut self, tags: CheetahString) {
287 self.properties
288 .as_map_mut()
289 .insert(CheetahString::from_static_str(MessageConst::PROPERTY_TAGS), tags);
290 }
291
292 #[inline]
293 pub fn set_keys(&mut self, keys: CheetahString) {
294 self.properties
295 .as_map_mut()
296 .insert(CheetahString::from_static_str(MessageConst::PROPERTY_KEYS), keys);
297 }
298
299 #[inline]
300 pub fn clear_property(&mut self, name: impl Into<CheetahString>) {
301 self.properties.as_map_mut().remove(name.into().as_str());
302 }
303
304 #[inline]
305 pub fn set_properties(&mut self, properties: HashMap<CheetahString, CheetahString>) {
306 self.properties = MessageProperties::from_map(properties);
307 }
308
309 #[inline]
310 pub fn get_property(&self, key: &CheetahString) -> Option<CheetahString> {
311 self.properties.as_map().get(key).cloned()
312 }
313
314 #[inline]
315 pub fn body(&self) -> Option<bytes::Bytes> {
316 self.body.raw().cloned()
317 }
318
319 #[inline]
320 pub fn flag(&self) -> i32 {
321 self.flag.bits()
322 }
323
324 #[inline]
325 pub fn topic(&self) -> &CheetahString {
326 &self.topic
327 }
328
329 #[inline]
330 pub fn properties(&self) -> &MessageProperties {
331 &self.properties
332 }
333
334 #[inline]
335 pub fn transaction_id(&self) -> Option<&str> {
336 self.transaction_id.as_deref()
337 }
338
339 #[inline]
340 pub fn get_transaction_id(&self) -> Option<&CheetahString> {
341 self.transaction_id.as_ref()
342 }
343
344 #[inline]
345 pub fn get_tags(&self) -> Option<CheetahString> {
346 self.properties.as_map().get(MessageConst::PROPERTY_TAGS).cloned()
347 }
348
349 #[inline]
350 pub fn is_wait_store_msg_ok(&self) -> bool {
351 self.properties.wait_store_msg_ok()
352 }
353
354 #[inline]
355 fn set_wait_store_msg_ok(&mut self, wait_store_msg_ok: bool) {
356 self.properties.as_map_mut().insert(
357 CheetahString::from_static_str(MessageConst::PROPERTY_WAIT_STORE_MSG_OK),
358 CheetahString::from_static_str(if wait_store_msg_ok { "true" } else { "false" }),
359 );
360 }
361
362 #[inline]
363 pub fn delay_time_level(&self) -> i32 {
364 self.properties.delay_level().unwrap_or(0)
365 }
366
367 #[inline]
368 pub fn set_delay_time_level(&mut self, level: i32) {
369 self.properties.as_map_mut().insert(
370 CheetahString::from_static_str(MessageConst::PROPERTY_DELAY_TIME_LEVEL),
371 CheetahString::from(level.to_string()),
372 );
373 }
374
375 #[inline]
376 pub fn get_user_property(&self, name: impl Into<CheetahString>) -> Option<CheetahString> {
377 self.properties.as_map().get(name.into().as_str()).cloned()
378 }
379
380 #[inline]
381 pub fn as_any(&self) -> &dyn Any {
382 self
383 }
384
385 #[inline]
386 pub fn set_instance_id(&mut self, instance_id: impl Into<CheetahString>) {
387 self.properties.as_map_mut().insert(
388 CheetahString::from_static_str(MessageConst::PROPERTY_INSTANCE_ID),
389 instance_id.into(),
390 );
391 }
392
393 #[inline]
399 pub fn body_slice(&self) -> &[u8] {
400 self.body.as_slice()
401 }
402
403 #[inline]
405 pub fn into_body(self) -> MessageBody {
406 self.body
407 }
408
409 #[inline]
411 pub fn tags(&self) -> Option<&str> {
412 self.properties.tags()
413 }
414
415 #[inline]
417 pub fn keys(&self) -> Option<Vec<String>> {
418 self.properties.keys()
419 }
420
421 #[inline]
423 pub fn property(&self, key: &str) -> Option<&str> {
424 self.properties.as_map().get(key).map(|s| s.as_str())
425 }
426
427 #[inline]
429 pub fn message_flag(&self) -> MessageFlag {
430 self.flag
431 }
432
433 #[inline]
435 pub fn wait_store_msg_ok(&self) -> bool {
436 self.is_wait_store_msg_ok()
437 }
438
439 #[inline]
441 pub fn delay_level(&self) -> i32 {
442 self.delay_time_level()
443 }
444
445 #[inline]
447 pub fn buyer_id(&self) -> Option<&str> {
448 self.properties.buyer_id()
449 }
450
451 #[inline]
453 pub fn priority(&self) -> i32 {
454 self.properties.priority()
455 }
456
457 #[inline]
459 pub fn try_set_priority(&mut self, priority: i32) -> rocketmq_error::RocketMQResult<()> {
460 if priority < 0 {
461 return Err(rocketmq_error::RocketMQError::illegal_argument(
462 "The priority must be greater than or equal to 0",
463 ));
464 }
465 self.properties.as_map_mut().insert(
466 CheetahString::from_static_str(MessageConst::PROPERTY_PRIORITY),
467 CheetahString::from(priority.to_string()),
468 );
469 Ok(())
470 }
471
472 #[inline]
474 pub fn instance_id(&self) -> Option<&str> {
475 self.properties.instance_id()
476 }
477
478 #[doc(hidden)]
482 #[inline]
483 pub fn topic_mut(&mut self) -> &mut CheetahString {
484 &mut self.topic
485 }
486
487 #[doc(hidden)]
489 #[inline]
490 pub fn flag_mut(&mut self) -> &mut MessageFlag {
491 &mut self.flag
492 }
493
494 #[doc(hidden)]
496 #[inline]
497 pub fn properties_mut(&mut self) -> &mut MessageProperties {
498 &mut self.properties
499 }
500
501 #[doc(hidden)]
503 #[inline]
504 pub fn body_mut(&mut self) -> &mut MessageBody {
505 &mut self.body
506 }
507
508 #[doc(hidden)]
510 #[inline]
511 pub fn transaction_id_mut(&mut self) -> &mut Option<CheetahString> {
512 &mut self.transaction_id
513 }
514
515 #[doc(hidden)]
517 #[inline]
518 pub fn set_topic(&mut self, topic: CheetahString) {
519 self.topic = topic;
520 }
521
522 #[doc(hidden)]
524 #[inline]
525 pub fn set_flag(&mut self, flag: i32) {
526 self.flag = MessageFlag::from_bits(flag);
527 }
528
529 #[doc(hidden)]
531 #[inline]
532 pub fn set_body(&mut self, body: Option<Bytes>) {
533 self.body = if let Some(b) = body {
534 MessageBody::from(b)
535 } else {
536 MessageBody::empty()
537 };
538 }
539
540 #[doc(hidden)]
542 #[inline]
543 pub fn take_body(&mut self) -> Option<Bytes> {
544 let old = std::mem::take(&mut self.body);
545 old.raw().cloned()
546 }
547
548 #[doc(hidden)]
550 #[inline]
551 pub fn compressed_body(&self) -> Option<&Bytes> {
552 self.body.compressed()
553 }
554}
555
556fn java_properties_to_string(properties: &HashMap<CheetahString, CheetahString>) -> String {
557 if properties.is_empty() {
558 return "{}".to_string();
559 }
560
561 let mut entries = properties
562 .iter()
563 .map(|(key, value)| (key.as_str(), value.as_str()))
564 .collect::<Vec<_>>();
565 entries.sort_unstable_by(|left, right| left.0.cmp(right.0).then_with(|| left.1.cmp(right.1)));
566
567 let body = entries
568 .into_iter()
569 .map(|(key, value)| format!("{key}={value}"))
570 .collect::<Vec<_>>()
571 .join(", ");
572 format!("{{{body}}}")
573}
574
575fn java_body_to_string(body: Option<&Bytes>) -> String {
576 match body {
577 Some(body) => {
578 let values = body
579 .iter()
580 .map(|byte| (*byte as i8).to_string())
581 .collect::<Vec<_>>()
582 .join(", ");
583 format!("[{values}]")
584 }
585 None => "null".to_string(),
586 }
587}
588
589impl Display for Message {
590 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
591 write!(
592 f,
593 "Message{{topic='{}', flag={}, properties={}, body={}, transactionId='{}'}}",
594 self.topic,
595 self.flag.bits(),
596 java_properties_to_string(self.properties.as_map()),
597 java_body_to_string(self.body.raw()),
598 self.transaction_id.as_deref().unwrap_or("null")
599 )
600 }
601}
602
603#[allow(unused_variables)]
604impl MessageTrait for Message {
605 #[inline]
606 fn put_property(&mut self, key: CheetahString, value: CheetahString) {
607 self.properties.as_map_mut().insert(key, value);
608 }
609
610 #[inline]
611 fn clear_property(&mut self, name: &str) {
612 self.properties.as_map_mut().remove(name);
613 }
614
615 #[inline]
616 fn property(&self, name: &CheetahString) -> Option<CheetahString> {
617 self.properties.as_map().get(name).cloned()
618 }
619
620 fn property_ref(&self, name: &CheetahString) -> Option<&CheetahString> {
621 self.properties.as_map().get(name)
622 }
623
624 #[inline]
625 fn topic(&self) -> &CheetahString {
626 &self.topic
627 }
628
629 #[inline]
630 fn set_topic(&mut self, topic: CheetahString) {
631 self.set_topic(topic);
632 }
633
634 #[inline]
635 fn get_flag(&self) -> i32 {
636 self.flag.bits()
637 }
638
639 #[inline]
640 fn set_flag(&mut self, flag: i32) {
641 self.set_flag(flag);
642 }
643
644 #[inline]
645 fn get_body(&self) -> Option<&Bytes> {
646 self.body.raw()
647 }
648
649 #[inline]
650 fn set_body(&mut self, body: Bytes) {
651 self.set_body(Some(body));
652 }
653
654 #[inline]
655 fn get_properties(&self) -> &HashMap<CheetahString, CheetahString> {
656 self.properties.as_map()
657 }
658
659 #[inline]
660 fn set_properties(&mut self, properties: HashMap<CheetahString, CheetahString>) {
661 self.properties = MessageProperties::from_map(properties);
662 }
663
664 #[inline]
665 fn transaction_id(&self) -> Option<&CheetahString> {
666 self.transaction_id.as_ref()
667 }
668
669 #[inline]
670 fn set_transaction_id(&mut self, transaction_id: CheetahString) {
671 *self.transaction_id_mut() = Some(transaction_id);
672 }
673
674 #[inline]
675 fn get_compressed_body_mut(&mut self) -> Option<&mut Bytes> {
676 self.body.compressed_mut().as_mut()
677 }
678
679 #[inline]
680 fn get_compressed_body(&self) -> Option<&Bytes> {
681 self.compressed_body()
682 }
683
684 #[inline]
685 fn set_compressed_body_mut(&mut self, compressed_body: Bytes) {
686 self.body.set_compressed(compressed_body);
687 }
688
689 #[inline]
690 fn take_body(&mut self) -> Option<Bytes> {
691 self.take_body()
692 }
693
694 #[inline]
695 fn as_any(&self) -> &dyn Any {
696 self
697 }
698
699 #[inline]
700 fn as_any_mut(&mut self) -> &mut dyn Any {
701 self
702 }
703}
704
705pub fn parse_topic_filter_type(sys_flag: i32) -> TopicFilterType {
706 if (sys_flag & MessageSysFlag::MULTI_TAGS_FLAG) == MessageSysFlag::MULTI_TAGS_FLAG {
707 TopicFilterType::MultiTag
708 } else {
709 TopicFilterType::SingleTag
710 }
711}
712
713pub fn tags_string2tags_code(tags: Option<&CheetahString>) -> i64 {
714 match tags {
715 Some(tags) if !tags.is_empty() => JavaStringHasher::hash_str(tags.as_str()) as i64,
716 _ => 0,
717 }
718}
719
720#[cfg(test)]
721#[allow(deprecated)]
722mod tests {
723 use bytes::Bytes;
724
725 use super::*;
726
727 #[test]
728 fn test_message_new() {
729 let msg = Message::new("test_topic", b"test_body");
730 assert_eq!(msg.topic().as_str(), "test_topic");
731 assert_eq!(msg.body().unwrap().as_ref(), b"test_body");
732 }
733
734 #[test]
735 fn test_message_new_with_bytes() {
736 let body = Bytes::from_static(b"test_body");
737 let msg = Message::new_with_bytes("test_topic", body.clone());
738 assert_eq!(msg.topic().as_str(), "test_topic");
739 assert_eq!(msg.body().unwrap(), body);
740 }
741
742 #[test]
743 fn test_message_new_with_vec() {
744 let body = vec![1u8, 2, 3, 4, 5];
745 let msg = Message::new_with_vec("test_topic", body.clone());
746 assert_eq!(msg.topic().as_str(), "test_topic");
747 assert_eq!(msg.body().unwrap().as_ref(), body.as_slice());
748 }
749
750 #[test]
751 fn test_message_with_tags() {
752 let msg = Message::with_tags("test_topic", "tag1", b"test_body");
753 assert_eq!(msg.topic().as_str(), "test_topic");
754 assert_eq!(msg.get_tags().unwrap().as_str(), "tag1");
755 }
756
757 #[test]
758 fn test_message_with_tags_bytes() {
759 let body = Bytes::from_static(b"test_body");
760 let msg = Message::with_tags_bytes("test_topic", "tag1", body.clone());
761 assert_eq!(msg.topic().as_str(), "test_topic");
762 assert_eq!(msg.get_tags().unwrap().as_str(), "tag1");
763 assert_eq!(msg.body().unwrap(), body);
764 }
765
766 #[test]
767 fn test_message_with_keys() {
768 let msg = Message::with_keys("test_topic", "tag1", "key1", b"test_body");
769 assert_eq!(msg.topic().as_str(), "test_topic");
770 assert_eq!(msg.get_tags().unwrap().as_str(), "tag1");
771 assert_eq!(
772 msg.properties()
773 .as_map()
774 .get(MessageConst::PROPERTY_KEYS)
775 .unwrap()
776 .as_str(),
777 "key1"
778 );
779 }
780
781 #[test]
782 fn test_message_with_keys_bytes() {
783 let body = Bytes::from_static(b"test_body");
784 let msg = Message::with_keys_bytes("test_topic", "tag1", "key1", body.clone());
785 assert_eq!(msg.topic().as_str(), "test_topic");
786 assert_eq!(msg.get_tags().unwrap().as_str(), "tag1");
787 assert_eq!(msg.body().unwrap(), body);
788 }
789
790 #[test]
791 fn test_message_with_details() {
792 let msg = Message::with_details("test_topic", "tag1", "key1", 0, b"test_body", true);
793 assert_eq!(msg.topic().as_str(), "test_topic");
794 assert_eq!(msg.get_tags().unwrap().as_str(), "tag1");
795 assert!(msg.is_wait_store_msg_ok());
796 assert_eq!(
797 msg.properties().as_map().get(MessageConst::PROPERTY_WAIT_STORE_MSG_OK),
798 Some(&CheetahString::from_static_str("true"))
799 );
800 }
801
802 #[test]
803 fn test_message_with_details_bytes() {
804 let body = Bytes::from_static(b"test_body");
805 let msg = Message::with_details_bytes("test_topic", "tag1", "key1", 0, body.clone(), false);
806 assert_eq!(msg.topic().as_str(), "test_topic");
807 assert!(!msg.is_wait_store_msg_ok());
808 assert_eq!(
809 msg.properties().as_map().get(MessageConst::PROPERTY_WAIT_STORE_MSG_OK),
810 Some(&CheetahString::from_static_str("false"))
811 );
812 assert_eq!(msg.body().unwrap(), body);
813 }
814
815 #[test]
816 fn test_message_priority_matches_java_semantics() {
817 let mut msg = Message::new("test_topic", b"test_body");
818 assert_eq!(msg.priority(), -1);
819
820 msg.try_set_priority(3).unwrap();
821 assert_eq!(msg.priority(), 3);
822 assert_eq!(
823 msg.properties().as_map().get(MessageConst::PROPERTY_PRIORITY),
824 Some(&CheetahString::from_static_str("3"))
825 );
826
827 let error = msg
828 .try_set_priority(-1)
829 .expect_err("negative priority should be rejected");
830 assert!(error.to_string().contains("greater than or equal to 0"));
831 }
832
833 #[test]
834 fn test_get_transaction_id_returns_cheetah_string_ref() {
835 let mut msg = Message::new("test_topic", b"test_body");
836 msg.set_transaction_id(CheetahString::from_static_str("tx-123"));
837
838 let transaction_id = msg.get_transaction_id();
839
840 assert_eq!(transaction_id, Some(&CheetahString::from_static_str("tx-123")));
841 }
842
843 #[test]
844 fn message_display_matches_java_to_string_shape() {
845 let mut msg = Message::with_tags("TopicA", "TagA", &[1, 255]);
846 msg.set_flag(7);
847 msg.set_transaction_id(CheetahString::from_static_str("tx-1"));
848
849 assert_eq!(
850 msg.to_string(),
851 "Message{topic='TopicA', flag=7, properties={TAGS=TagA, WAIT=true}, body=[1, -1], transactionId='tx-1'}"
852 );
853 }
854
855 #[test]
856 fn empty_message_display_uses_java_array_null_wording_for_absent_body() {
857 let msg = Message::default();
858
859 assert_eq!(
860 msg.to_string(),
861 "Message{topic='', flag=0, properties={}, body=null, transactionId='null'}"
862 );
863 }
864
865 #[test]
866 fn test_properties_capacity_optimization() {
867 let msg1 = Message::with_details_bytes(
869 "topic",
870 CheetahString::empty(),
871 CheetahString::empty(),
872 0,
873 Bytes::from_static(b"body"),
874 true,
875 );
876 assert_eq!(msg1.properties().len(), 1);
877 assert_eq!(
878 msg1.properties().as_map().get(MessageConst::PROPERTY_WAIT_STORE_MSG_OK),
879 Some(&CheetahString::from_static_str("true"))
880 );
881
882 let msg2 = Message::with_details_bytes(
884 "topic",
885 "tag1",
886 CheetahString::empty(),
887 0,
888 Bytes::from_static(b"body"),
889 true,
890 );
891 assert_eq!(msg2.properties().len(), 2);
892
893 let msg3 = Message::with_details_bytes("topic", "tag1", "key1", 0, Bytes::from_static(b"body"), true);
895 assert_eq!(msg3.properties().len(), 3);
896
897 let msg4 = Message::with_details_bytes("topic", "tag1", "key1", 0, Bytes::from_static(b"body"), false);
899 assert_eq!(msg4.properties().len(), 3);
900 assert_eq!(
901 msg4.properties().as_map().get(MessageConst::PROPERTY_WAIT_STORE_MSG_OK),
902 Some(&CheetahString::from_static_str("false"))
903 );
904 }
905
906 #[test]
907 fn test_zero_copy_bytes() {
908 let original_bytes = Bytes::from_static(b"test_data");
909 let bytes_clone = original_bytes.clone();
910
911 let msg = Message::new_with_bytes("topic", bytes_clone);
913
914 let body = msg.body().unwrap();
916 assert_eq!(body.as_ptr(), original_bytes.as_ptr());
917 }
918
919 #[test]
920 fn test_put_user_property_error_handling() {
921 use rocketmq_error::RocketMQError;
922
923 let mut msg = Message::new("test_topic", b"test body");
924
925 let result = msg.put_user_property(CheetahString::empty(), CheetahString::from_slice("value"));
927 assert!(result.is_err());
928 if let Err(RocketMQError::InvalidProperty(e)) = result {
929 assert!(e.contains("null or blank"));
930 }
931
932 let result = msg.put_user_property(CheetahString::from_slice("name"), CheetahString::empty());
934 assert!(result.is_err());
935 if let Err(RocketMQError::InvalidProperty(e)) = result {
936 assert!(e.contains("null or blank"));
937 }
938
939 let result = msg.put_user_property(CheetahString::from_slice("KEYS"), CheetahString::from_slice("value"));
941 assert!(result.is_err());
942 if let Err(RocketMQError::InvalidProperty(e)) = result {
943 assert!(e.contains("used by system"));
944 }
945
946 let result = msg.put_user_property(
948 CheetahString::from_static_str(MessageConst::PROPERTY_PRIORITY),
949 CheetahString::from_slice("value"),
950 );
951 assert!(result.is_err());
952 if let Err(RocketMQError::InvalidProperty(e)) = result {
953 assert!(e.contains("used by system"));
954 }
955
956 let result = msg.put_user_property(
958 CheetahString::from_static_str(MessageConst::PROPERTY_ORIGIN_GROUP),
959 CheetahString::from_slice("value"),
960 );
961 assert!(result.is_err());
962 if let Err(RocketMQError::InvalidProperty(e)) = result {
963 assert!(e.contains("used by system"));
964 }
965
966 let result = msg.put_user_property(
968 CheetahString::from_slice("my_custom_key"),
969 CheetahString::from_slice("my_value"),
970 );
971 assert!(result.is_ok());
972 assert_eq!(
973 msg.get_user_property(CheetahString::from_slice("my_custom_key"))
974 .unwrap()
975 .as_str(),
976 "my_value"
977 );
978 }
979}