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