Skip to main content

rocketmq_common/common/message/
message_single.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::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
53// Private internal constructor used by builder
54impl 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    /// Creates a new message builder.
86    ///
87    /// This is the recommended way to create messages.
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// use rocketmq_common::common::message::message_single::Message;
93    ///
94    /// let msg = Message::builder()
95    ///     .topic("test-topic")
96    ///     .body_slice(b"hello world")
97    ///     .tags("important")
98    ///     .build_unchecked();
99    /// ```
100    pub fn builder() -> MessageBuilder {
101        MessageBuilder::new()
102    }
103
104    /// Create a new message with topic and body slice (will copy)
105    #[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    /// Create a new message with topic and Bytes (zero-copy)
113    #[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    /// Create a new message with topic and Vec<u8> (zero-copy conversion to Bytes)
121    #[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    /// Create a message with tags and body slice (will copy)
143    #[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    /// Create a message with tags and Bytes (zero-copy)
151    #[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    /// Create a message with keys and body slice (will copy)
159    #[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    /// Create a message with keys and Bytes (zero-copy)
172    #[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    /// Create message with body slice (will copy data)
185    #[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    /// Create message with Bytes (zero-copy)
199    #[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        // Pre-allocate HashMap with estimated capacity to avoid reallocation
213        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        // Use static strings for keys to avoid allocations
219        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        // Pre-allocate HashMap with estimated capacity
254        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    // ===== New Rust-idiomatic API methods =====
394
395    /// Returns the message body as a byte slice (borrows).
396    ///
397    /// This is the recommended way to access the message body.
398    #[inline]
399    pub fn body_slice(&self) -> &[u8] {
400        self.body.as_slice()
401    }
402
403    /// Consumes the message and returns the body.
404    #[inline]
405    pub fn into_body(self) -> MessageBody {
406        self.body
407    }
408
409    /// Returns the message tags.
410    #[inline]
411    pub fn tags(&self) -> Option<&str> {
412        self.properties.tags()
413    }
414
415    /// Returns the message keys as a vector.
416    #[inline]
417    pub fn keys(&self) -> Option<Vec<String>> {
418        self.properties.keys()
419    }
420
421    /// Returns a property value by key.
422    #[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    /// Returns the message flag as a type-safe MessageFlag.
428    #[inline]
429    pub fn message_flag(&self) -> MessageFlag {
430        self.flag
431    }
432
433    /// Returns whether to wait for store confirmation.
434    #[inline]
435    pub fn wait_store_msg_ok(&self) -> bool {
436        self.is_wait_store_msg_ok()
437    }
438
439    /// Returns the delay time level.
440    #[inline]
441    pub fn delay_level(&self) -> i32 {
442        self.delay_time_level()
443    }
444
445    /// Returns the buyer ID.
446    #[inline]
447    pub fn buyer_id(&self) -> Option<&str> {
448        self.properties.buyer_id()
449    }
450
451    /// Returns the message priority, or `-1` if not set or not parseable.
452    #[inline]
453    pub fn priority(&self) -> i32 {
454        self.properties.priority()
455    }
456
457    /// Sets the message priority with Java-compatible non-negative validation.
458    #[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    /// Returns the instance ID.
473    #[inline]
474    pub fn instance_id(&self) -> Option<&str> {
475        self.properties.instance_id()
476    }
477
478    // ===== Internal accessors for other modules =====
479
480    /// Returns a mutable reference to the topic (internal use only).
481    #[doc(hidden)]
482    #[inline]
483    pub fn topic_mut(&mut self) -> &mut CheetahString {
484        &mut self.topic
485    }
486
487    /// Returns a mutable reference to the flag (internal use only).
488    #[doc(hidden)]
489    #[inline]
490    pub fn flag_mut(&mut self) -> &mut MessageFlag {
491        &mut self.flag
492    }
493
494    /// Returns a mutable reference to properties (internal use only).
495    #[doc(hidden)]
496    #[inline]
497    pub fn properties_mut(&mut self) -> &mut MessageProperties {
498        &mut self.properties
499    }
500
501    /// Returns a mutable reference to the body (internal use only).
502    #[doc(hidden)]
503    #[inline]
504    pub fn body_mut(&mut self) -> &mut MessageBody {
505        &mut self.body
506    }
507
508    /// Returns a mutable reference to the transaction ID (internal use only).
509    #[doc(hidden)]
510    #[inline]
511    pub fn transaction_id_mut(&mut self) -> &mut Option<CheetahString> {
512        &mut self.transaction_id
513    }
514
515    /// Sets the topic (internal use only).
516    #[doc(hidden)]
517    #[inline]
518    pub fn set_topic(&mut self, topic: CheetahString) {
519        self.topic = topic;
520    }
521
522    /// Sets the flag (internal use only).
523    #[doc(hidden)]
524    #[inline]
525    pub fn set_flag(&mut self, flag: i32) {
526        self.flag = MessageFlag::from_bits(flag);
527    }
528
529    /// Sets the body (internal use only).
530    #[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    /// Takes ownership of the body, leaving empty (internal use only).
541    #[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    /// Returns a reference to the compressed body (internal use only).
549    #[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        // Test with no tags or keys
868        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        // Test with tags
883        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        // Test with tags and keys
894        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        // Test with wait_store_msg_ok = false
898        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        // Creating message with Bytes should not copy
912        let msg = Message::new_with_bytes("topic", bytes_clone);
913
914        // The body should share the same underlying data
915        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        // Test empty name
926        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        // Test empty value
933        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        // Test system reserved property
940        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        // Test Java-reserved priority property
947        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        // Test Java-reserved origin group property
957        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        // Test valid user property
967        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}