rocketmq_model/common/message/
message_builder.rs1use bytes::Bytes;
16use cheetah_string::CheetahString;
17use rocketmq_error::Result;
18
19use crate::common::message::message_body::MessageBody;
20use crate::common::message::message_flag::MessageFlag;
21use crate::common::message::message_property::MessageProperties;
22use crate::common::message::message_property::MessagePropertyKey;
23use crate::common::message::MessageConst;
24
25#[derive(Default)]
40pub struct MessageBuilder {
41 topic: Option<CheetahString>,
42 body: MessageBody,
43 properties: MessageProperties,
44 flag: MessageFlag,
45 transaction_id: Option<CheetahString>,
46}
47
48impl MessageBuilder {
49 #[inline]
51 pub fn new() -> Self {
52 Self::default()
53 }
54
55 #[inline]
57 pub fn topic(mut self, topic: impl Into<CheetahString>) -> Self {
58 self.topic = Some(topic.into());
59 self
60 }
61
62 #[inline]
64 pub fn body(mut self, body: impl Into<Bytes>) -> Self {
65 self.body = MessageBody::new(body);
66 self
67 }
68
69 #[inline]
71 pub fn body_slice(mut self, body: &[u8]) -> Self {
72 self.body = MessageBody::new(Bytes::copy_from_slice(body));
73 self
74 }
75
76 #[inline]
78 pub fn empty_body(mut self) -> Self {
79 self.body = MessageBody::empty();
80 self
81 }
82
83 #[inline]
85 pub fn tags(mut self, tags: impl Into<CheetahString>) -> Self {
86 self.properties.insert(MessagePropertyKey::Tags, tags.into());
87 self
88 }
89
90 #[inline]
92 pub fn keys(mut self, keys: Vec<String>) -> Self {
93 if !keys.is_empty() {
94 let keys_str = keys.join(MessageConst::KEY_SEPARATOR);
95 self.properties.insert(MessagePropertyKey::Keys, keys_str);
96 }
97 self
98 }
99
100 #[inline]
102 pub fn key(mut self, key: impl Into<CheetahString>) -> Self {
103 self.properties.insert(MessagePropertyKey::Keys, key.into());
104 self
105 }
106
107 #[inline]
109 pub fn flag(mut self, flag: MessageFlag) -> Self {
110 self.flag = flag;
111 self
112 }
113
114 #[inline]
116 pub fn flag_bits(mut self, bits: i32) -> Self {
117 self.flag = MessageFlag::from_bits(bits);
118 self
119 }
120
121 #[inline]
123 pub fn delay_level(mut self, level: i32) -> Self {
124 self.properties
125 .insert(MessagePropertyKey::DelayTimeLevel, level.to_string());
126 self
127 }
128
129 #[inline]
131 pub fn delay_secs(mut self, secs: u64) -> Self {
132 self.properties
133 .insert(MessagePropertyKey::DelayTimeSec, secs.to_string());
134 self
135 }
136
137 #[inline]
139 pub fn delay_millis(mut self, millis: u64) -> Self {
140 self.properties
141 .insert(MessagePropertyKey::DelayTimeMs, millis.to_string());
142 self
143 }
144
145 #[inline]
147 pub fn deliver_time_ms(mut self, time_ms: u64) -> Self {
148 self.properties
149 .insert(MessagePropertyKey::DeliverTimeMs, time_ms.to_string());
150 self
151 }
152
153 #[inline]
155 pub fn priority(mut self, priority: u32) -> Self {
156 self.properties
157 .insert(MessagePropertyKey::Priority, priority.to_string());
158 self
159 }
160
161 pub fn try_priority(mut self, priority: i32) -> Result<Self> {
163 if priority < 0 {
164 return Err(crate::error::invalid_argument(
165 "The priority must be greater than or equal to 0",
166 ));
167 }
168 self.properties
169 .insert(MessagePropertyKey::Priority, priority.to_string());
170 Ok(self)
171 }
172
173 #[inline]
175 pub fn wait_store_msg_ok(mut self, wait: bool) -> Self {
176 self.properties
177 .insert(MessagePropertyKey::WaitStoreMsgOk, if wait { "true" } else { "false" });
178 self
179 }
180
181 #[inline]
183 pub fn transaction_id(mut self, id: impl Into<CheetahString>) -> Self {
184 self.transaction_id = Some(id.into());
185 self
186 }
187
188 #[inline]
190 pub fn buyer_id(mut self, buyer_id: impl Into<CheetahString>) -> Self {
191 self.properties.insert(MessagePropertyKey::BuyerId, buyer_id.into());
192 self
193 }
194
195 #[inline]
197 pub fn instance_id(mut self, instance_id: impl Into<CheetahString>) -> Self {
198 self.properties
199 .insert(MessagePropertyKey::InstanceId, instance_id.into());
200 self
201 }
202
203 #[inline]
205 pub fn correlation_id(mut self, correlation_id: impl Into<CheetahString>) -> Self {
206 self.properties
207 .insert(MessagePropertyKey::CorrelationId, correlation_id.into());
208 self
209 }
210
211 #[inline]
213 pub fn sharding_key(mut self, sharding_key: impl Into<CheetahString>) -> Self {
214 self.properties
215 .insert(MessagePropertyKey::ShardingKey, sharding_key.into());
216 self
217 }
218
219 #[inline]
221 pub fn trace_switch(mut self, enabled: bool) -> Self {
222 self.properties
223 .insert(MessagePropertyKey::TraceSwitch, if enabled { "true" } else { "false" });
224 self
225 }
226
227 #[inline]
229 pub fn property(mut self, key: MessagePropertyKey, value: impl Into<CheetahString>) -> Self {
230 self.properties.insert(key, value.into());
231 self
232 }
233
234 pub fn raw_property(mut self, key: impl Into<CheetahString>, value: impl Into<CheetahString>) -> Result<Self> {
240 let key = key.into();
241 let value = value.into();
242
243 if crate::common::message::STRING_HASH_SET.contains(key.as_str()) {
245 return Err(crate::error::invalid_property(format!(
246 "The Property<{key}> is used by system, input another please"
247 )));
248 }
249
250 self.properties.as_map_mut().insert(key, value);
251 Ok(self)
252 }
253
254 pub fn build(mut self) -> Result<super::message_single::Message> {
260 let topic = self
261 .topic
262 .ok_or_else(|| crate::error::invalid_property("Topic is required for message"))?;
263 if self.properties.get(MessagePropertyKey::WaitStoreMsgOk).is_none() {
264 self.properties.insert(MessagePropertyKey::WaitStoreMsgOk, "true");
265 }
266
267 Ok(super::message_single::Message::from_builder(
268 topic,
269 self.body,
270 self.properties,
271 self.flag,
272 self.transaction_id,
273 ))
274 }
275
276 #[track_caller]
280 pub fn build_unchecked(self) -> super::message_single::Message {
281 self.build().expect("message validation failed")
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 #[test]
290 fn test_builder_basic() {
291 let msg = MessageBuilder::new()
292 .topic("test-topic")
293 .body_slice(b"hello world")
294 .build_unchecked();
295
296 assert_eq!(msg.topic().as_str(), "test-topic");
297 assert_eq!(msg.body_slice(), b"hello world");
298 assert_eq!(msg.properties().get(MessagePropertyKey::WaitStoreMsgOk), Some("true"));
299 }
300
301 #[test]
302 fn test_builder_with_tags_and_keys() {
303 let msg = MessageBuilder::new()
304 .topic("test-topic")
305 .body_slice(b"test")
306 .tags("tag1")
307 .keys(vec!["key1".to_string(), "key2".to_string()])
308 .build_unchecked();
309
310 assert_eq!(msg.tags(), Some("tag1"));
311 assert_eq!(msg.keys(), Some(vec!["key1".to_string(), "key2".to_string()]));
312 }
313
314 #[test]
315 fn test_builder_with_delay() {
316 let msg = MessageBuilder::new()
317 .topic("test-topic")
318 .body_slice(b"test")
319 .delay_level(3)
320 .build_unchecked();
321
322 assert_eq!(msg.delay_time_level(), 3);
323 }
324
325 #[test]
326 fn test_builder_with_priority_matches_java_property() {
327 let msg = MessageBuilder::new()
328 .topic("test-topic")
329 .body_slice(b"test")
330 .priority(7)
331 .build_unchecked();
332
333 assert_eq!(msg.priority(), 7);
334 assert_eq!(msg.properties().get(MessagePropertyKey::Priority), Some("7"));
335 }
336
337 #[test]
338 fn test_builder_try_priority_rejects_negative_like_java() {
339 match MessageBuilder::new().topic("test-topic").try_priority(-1) {
340 Ok(_) => panic!("negative priority should be rejected"),
341 Err(error) => assert_eq!(error.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID),
342 }
343 }
344
345 #[test]
346 fn test_builder_missing_topic() {
347 let result = MessageBuilder::new().body_slice(b"test").build();
348
349 assert!(result.is_err());
350 }
351
352 #[test]
353 fn test_builder_with_transaction_id() {
354 let msg = MessageBuilder::new()
355 .topic("test-topic")
356 .body_slice(b"test")
357 .transaction_id("tx-123")
358 .build_unchecked();
359
360 assert_eq!(msg.transaction_id(), Some("tx-123"));
361 }
362
363 #[test]
364 fn test_builder_wait_store_msg_ok_writes_java_boolean_property() {
365 let wait_msg = MessageBuilder::new()
366 .topic("test-topic")
367 .body_slice(b"test")
368 .wait_store_msg_ok(true)
369 .build_unchecked();
370 assert!(wait_msg.wait_store_msg_ok());
371 assert_eq!(
372 wait_msg.properties().get(MessagePropertyKey::WaitStoreMsgOk),
373 Some("true")
374 );
375
376 let no_wait_msg = MessageBuilder::new()
377 .topic("test-topic")
378 .body_slice(b"test")
379 .wait_store_msg_ok(false)
380 .build_unchecked();
381 assert!(!no_wait_msg.wait_store_msg_ok());
382 assert_eq!(
383 no_wait_msg.properties().get(MessagePropertyKey::WaitStoreMsgOk),
384 Some("false")
385 );
386 }
387}