1use cheetah_string::CheetahString;
16use rocketmq_macros::RequestHeaderCodec;
17use serde::Deserialize;
18use serde::Serialize;
19
20use crate::code::request_code::RequestCode;
21use crate::protocol::header::message_operation_header::send_message_request_header_v2::SendMessageRequestHeaderV2;
22use crate::protocol::header::message_operation_header::TopicRequestHeaderTrait;
23use crate::protocol::remoting_command::RemotingCommand;
24use crate::rpc::rpc_request_header::RpcRequestHeader;
25use crate::rpc::topic_request_header::TopicRequestHeader;
26
27#[derive(Debug, Clone, Serialize, Deserialize, Default, RequestHeaderCodec)]
28#[header(
29 type_id = "rocketmq_protocol::protocol::header::message_operation_header::send_message_request_header::SendMessageRequestHeader",
30 java_class = "org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader",
31 lookup = "scan",
32 fast
33)]
34#[serde(rename_all = "camelCase")]
35pub struct SendMessageRequestHeader {
36 #[header(required)]
37 pub producer_group: CheetahString,
38
39 #[header(required)]
40 pub topic: CheetahString,
41
42 #[header(required)]
43 pub default_topic: CheetahString,
44
45 #[header(required)]
46 pub default_topic_queue_nums: i32,
47
48 #[header(required)]
49 pub queue_id: i32,
50
51 #[header(required)]
52 pub sys_flag: i32,
53
54 #[header(required)]
55 pub born_timestamp: i64,
56
57 #[header(required)]
58 pub flag: i32,
59
60 pub properties: Option<CheetahString>,
61 pub reconsume_times: Option<i32>,
62 pub unit_mode: Option<bool>,
63 pub batch: Option<bool>,
64 pub max_reconsume_times: Option<i32>,
65 #[serde(flatten)]
66 #[header(flatten, presence = "always")]
67 pub topic_request_header: Option<TopicRequestHeader>,
68}
69
70impl SendMessageRequestHeader {
71 #[inline(always)]
72 pub fn is_batch(&self) -> bool {
73 self.batch.unwrap_or_default()
74 }
75
76 #[inline]
77 fn topic_request_header_mut(&mut self) -> &mut TopicRequestHeader {
78 self.topic_request_header
79 .get_or_insert_with(TopicRequestHeader::default)
80 }
81
82 #[inline]
83 fn rpc_request_header_mut(&mut self) -> &mut RpcRequestHeader {
84 self.topic_request_header_mut()
85 .rpc_request_header
86 .get_or_insert_with(RpcRequestHeader::default)
87 }
88}
89
90impl TopicRequestHeaderTrait for SendMessageRequestHeader {
91 fn set_lo(&mut self, lo: Option<bool>) {
92 self.topic_request_header_mut().lo = lo;
93 }
94
95 fn lo(&self) -> Option<bool> {
96 match self.topic_request_header {
97 None => None,
98 Some(ref value) => value.lo,
99 }
100 }
101
102 fn set_topic(&mut self, topic: CheetahString) {
103 self.topic = topic;
104 }
105
106 fn topic(&self) -> &CheetahString {
107 &self.topic
108 }
109
110 fn broker_name(&self) -> Option<&CheetahString> {
111 self.topic_request_header
112 .as_ref()?
113 .rpc_request_header
114 .as_ref()?
115 .broker_name
116 .as_ref()
117 }
118
119 fn set_broker_name(&mut self, broker_name: CheetahString) {
120 self.rpc_request_header_mut().broker_name = Some(broker_name);
121 }
122
123 fn namespace(&self) -> Option<&str> {
124 self.topic_request_header
125 .as_ref()?
126 .rpc_request_header
127 .as_ref()?
128 .namespace
129 .as_deref()
130 }
131
132 fn set_namespace(&mut self, namespace: CheetahString) {
133 self.rpc_request_header_mut().namespace = Some(namespace);
134 }
135
136 fn namespaced(&self) -> Option<bool> {
137 self.topic_request_header
138 .as_ref()?
139 .rpc_request_header
140 .as_ref()?
141 .namespaced
142 .as_ref()
143 .cloned()
144 }
145
146 fn set_namespaced(&mut self, namespaced: bool) {
147 self.rpc_request_header_mut().namespaced = Some(namespaced);
148 }
149
150 fn oneway(&self) -> Option<bool> {
151 self.topic_request_header
152 .as_ref()?
153 .rpc_request_header
154 .as_ref()?
155 .oneway
156 .as_ref()
157 .cloned()
158 }
159
160 fn set_oneway(&mut self, oneway: bool) {
161 self.rpc_request_header_mut().oneway = Some(oneway);
162 }
163
164 fn queue_id(&self) -> i32 {
165 self.queue_id
166 }
167
168 fn set_queue_id(&mut self, queue_id: i32) {
169 self.queue_id = queue_id;
170 }
171}
172
173#[inline]
192pub fn parse_request_header(
193 request: &RemotingCommand,
194 request_code: RequestCode,
195) -> rocketmq_error::Result<SendMessageRequestHeader> {
196 let mut request_header_v2 = None;
197 if RequestCode::SendMessageV2 == request_code || RequestCode::SendBatchMessage == request_code {
198 request_header_v2 = request
200 .decode_command_custom_header_fast::<SendMessageRequestHeaderV2>()
201 .ok();
202 }
203 match request_header_v2 {
205 Some(header) => Ok(SendMessageRequestHeaderV2::create_send_message_request_header_v1(
206 &header,
207 )),
208 None => request.decode_command_custom_header_fast::<SendMessageRequestHeader>(),
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use std::collections::HashMap;
215
216 use cheetah_string::CheetahString;
217
218 use super::*;
219 use crate::code::request_code::RequestCode;
220 use crate::protocol::command_custom_header::CommandCustomHeader;
221 use crate::protocol::command_custom_header::FromMap;
222 use crate::protocol::remoting_command::RemotingCommand;
223
224 fn minimal_header() -> SendMessageRequestHeader {
225 SendMessageRequestHeader {
226 producer_group: CheetahString::from_static_str("test_producer_group"),
227 topic: CheetahString::from_static_str("test_topic"),
228 default_topic: CheetahString::from_static_str("test_default_topic"),
229 default_topic_queue_nums: 8,
230 queue_id: 1,
231 sys_flag: 0,
232 born_timestamp: 1622547800000,
233 flag: 0,
234 properties: None,
235 reconsume_times: None,
236 unit_mode: None,
237 batch: None,
238 max_reconsume_times: None,
239 topic_request_header: None,
240 }
241 }
242
243 fn assert_same_send_message_header(left: &SendMessageRequestHeader, right: &SendMessageRequestHeader) {
244 assert_eq!(left.producer_group, right.producer_group);
245 assert_eq!(left.topic, right.topic);
246 assert_eq!(left.default_topic, right.default_topic);
247 assert_eq!(left.default_topic_queue_nums, right.default_topic_queue_nums);
248 assert_eq!(left.queue_id, right.queue_id);
249 assert_eq!(left.sys_flag, right.sys_flag);
250 assert_eq!(left.born_timestamp, right.born_timestamp);
251 assert_eq!(left.flag, right.flag);
252 assert_eq!(left.properties, right.properties);
253 assert_eq!(left.reconsume_times, right.reconsume_times);
254 assert_eq!(left.unit_mode, right.unit_mode);
255 assert_eq!(left.batch, right.batch);
256 assert_eq!(left.max_reconsume_times, right.max_reconsume_times);
257 }
258
259 #[test]
260 fn send_message_request_header_fast_decode_matches_normal_decode() {
261 let mut header = minimal_header();
262 header.properties = Some(CheetahString::from_static_str("KEYS=abc"));
263 header.reconsume_times = Some(2);
264 header.unit_mode = Some(true);
265 header.batch = Some(false);
266 header.max_reconsume_times = Some(16);
267 let request =
268 RemotingCommand::create_remoting_command(RequestCode::SendMessage).set_ext_fields(header.to_map().unwrap());
269
270 let normal = request
271 .decode_command_custom_header::<SendMessageRequestHeader>()
272 .expect("normal decode should succeed");
273 let fast = request
274 .decode_command_custom_header_fast::<SendMessageRequestHeader>()
275 .expect("fast decode should succeed");
276 let parsed = parse_request_header(&request, RequestCode::SendMessage).expect("parse should use fast decode");
277
278 assert_same_send_message_header(&normal, &fast);
279 assert_same_send_message_header(&normal, &parsed);
280 }
281
282 #[test]
283 fn send_message_v2_fast_decode_matches_normal_v1_conversion() {
284 let mut header = minimal_header();
285 header.properties = Some(CheetahString::from_static_str("TAGS=TagA"));
286 header.reconsume_times = Some(1);
287 header.unit_mode = Some(false);
288 header.batch = Some(true);
289 header.max_reconsume_times = Some(8);
290 header.set_broker_name(CheetahString::from_static_str("broker-a"));
291 let v2 = SendMessageRequestHeaderV2::create_send_message_request_header_v2(&header);
292 let request =
293 RemotingCommand::create_remoting_command(RequestCode::SendMessageV2).set_ext_fields(v2.to_map().unwrap());
294
295 let normal_v2 = request
296 .decode_command_custom_header::<SendMessageRequestHeaderV2>()
297 .expect("normal V2 decode should succeed");
298 let fast_v2 = request
299 .decode_command_custom_header_fast::<SendMessageRequestHeaderV2>()
300 .expect("fast V2 decode should succeed");
301 let normal_v1 = SendMessageRequestHeaderV2::create_send_message_request_header_v1(&normal_v2);
302 let fast_v1 = SendMessageRequestHeaderV2::create_send_message_request_header_v1(&fast_v2);
303 let parsed =
304 parse_request_header(&request, RequestCode::SendMessageV2).expect("parse should use V2 fast decode");
305
306 assert_same_send_message_header(&normal_v1, &fast_v1);
307 assert_same_send_message_header(&normal_v1, &parsed);
308 assert_eq!(parsed.broker_name().map(CheetahString::as_str), Some("broker-a"));
309 }
310
311 #[test]
312 fn parse_request_header_handles_invalid_request_code() {
313 let request = RemotingCommand::create_remoting_command(RequestCode::SendBatchMessage);
314 let request_code = RequestCode::SendBatchMessage;
315 let result = parse_request_header(&request, request_code);
316 assert!(result.is_err());
317 }
318
319 #[test]
320 fn parse_request_header_handles_missing_header() {
321 let request = RemotingCommand::create_remoting_command(RequestCode::SendMessageV2);
322 let request_code = RequestCode::SendMessageV2;
323 let result = parse_request_header(&request, request_code);
324 assert!(result.is_err());
325 }
326
327 #[test]
328 fn send_message_request_header_serializes_correctly() {
329 let header = SendMessageRequestHeader {
330 producer_group: CheetahString::from_static_str("test_producer_group"),
331 topic: CheetahString::from_static_str("test_topic"),
332 default_topic: CheetahString::from_static_str("test_default_topic"),
333 default_topic_queue_nums: 8,
334 queue_id: 1,
335 sys_flag: 0,
336 born_timestamp: 1622547800000,
337 flag: 0,
338 properties: Some(CheetahString::from_static_str("test_properties")),
339 reconsume_times: Some(3),
340 unit_mode: Some(true),
341 batch: Some(false),
342 max_reconsume_times: Some(5),
343 topic_request_header: None,
344 };
345 let map = header.to_map().unwrap();
346 assert_eq!(
347 map.get(&CheetahString::from_static_str("producerGroup")).unwrap(),
348 "test_producer_group"
349 );
350 assert_eq!(map.get(&CheetahString::from_static_str("topic")).unwrap(), "test_topic");
351 assert_eq!(
352 map.get(&CheetahString::from_static_str("defaultTopic")).unwrap(),
353 "test_default_topic"
354 );
355 assert_eq!(
356 map.get(&CheetahString::from_static_str("defaultTopicQueueNums"))
357 .unwrap(),
358 "8"
359 );
360 assert_eq!(map.get(&CheetahString::from_static_str("queueId")).unwrap(), "1");
361 assert_eq!(map.get(&CheetahString::from_static_str("sysFlag")).unwrap(), "0");
362 assert_eq!(
363 map.get(&CheetahString::from_static_str("bornTimestamp")).unwrap(),
364 "1622547800000"
365 );
366 assert_eq!(map.get(&CheetahString::from_static_str("flag")).unwrap(), "0");
367 assert_eq!(
368 map.get(&CheetahString::from_static_str("properties")).unwrap(),
369 "test_properties"
370 );
371 assert_eq!(map.get(&CheetahString::from_static_str("reconsumeTimes")).unwrap(), "3");
372 assert_eq!(map.get(&CheetahString::from_static_str("unitMode")).unwrap(), "true");
373 assert_eq!(map.get(&CheetahString::from_static_str("batch")).unwrap(), "false");
374 assert_eq!(
375 map.get(&CheetahString::from_static_str("maxReconsumeTimes")).unwrap(),
376 "5"
377 );
378 }
379
380 #[test]
381 fn topic_request_header_setters_initialize_missing_nested_headers() {
382 let mut header = minimal_header();
383
384 header.set_lo(Some(true));
385 header.set_broker_name(CheetahString::from_static_str("broker-a"));
386 header.set_namespace(CheetahString::from_static_str("ns-a"));
387 header.set_namespaced(true);
388 header.set_oneway(true);
389
390 assert_eq!(header.lo(), Some(true));
391 assert_eq!(header.broker_name().map(CheetahString::as_str), Some("broker-a"));
392 assert_eq!(header.namespace(), Some("ns-a"));
393 assert_eq!(header.namespaced(), Some(true));
394 assert_eq!(header.oneway(), Some(true));
395
396 let map = header.to_map().expect("header should encode");
397 assert_eq!(map.get("lo").map(CheetahString::as_str), Some("true"));
398 assert_eq!(map.get("bname").map(CheetahString::as_str), Some("broker-a"));
399 assert_eq!(map.get("ns").map(CheetahString::as_str), Some("ns-a"));
400 assert_eq!(map.get("nsd").map(CheetahString::as_str), Some("true"));
401 assert_eq!(map.get("oway").map(CheetahString::as_str), Some("true"));
402 }
403
404 #[test]
405 fn send_message_request_header_deserializes_correctly() {
406 let mut map = HashMap::new();
407 map.insert(
408 CheetahString::from_static_str("producerGroup"),
409 CheetahString::from_static_str("test_producer_group"),
410 );
411 map.insert(
412 CheetahString::from_static_str("topic"),
413 CheetahString::from_static_str("test_topic"),
414 );
415 map.insert(
416 CheetahString::from_static_str("defaultTopic"),
417 CheetahString::from_static_str("test_default_topic"),
418 );
419 map.insert(
420 CheetahString::from_static_str("defaultTopicQueueNums"),
421 CheetahString::from_static_str("8"),
422 );
423 map.insert(
424 CheetahString::from_static_str("queueId"),
425 CheetahString::from_static_str("1"),
426 );
427 map.insert(
428 CheetahString::from_static_str("sysFlag"),
429 CheetahString::from_static_str("0"),
430 );
431 map.insert(
432 CheetahString::from_static_str("bornTimestamp"),
433 CheetahString::from_static_str("1622547800000"),
434 );
435 map.insert(
436 CheetahString::from_static_str("flag"),
437 CheetahString::from_static_str("0"),
438 );
439 map.insert(
440 CheetahString::from_static_str("properties"),
441 CheetahString::from_static_str("test_properties"),
442 );
443 map.insert(
444 CheetahString::from_static_str("reconsumeTimes"),
445 CheetahString::from_static_str("3"),
446 );
447 map.insert(
448 CheetahString::from_static_str("unitMode"),
449 CheetahString::from_static_str("true"),
450 );
451 map.insert(
452 CheetahString::from_static_str("batch"),
453 CheetahString::from_static_str("false"),
454 );
455 map.insert(
456 CheetahString::from_static_str("maxReconsumeTimes"),
457 CheetahString::from_static_str("5"),
458 );
459
460 let header = <SendMessageRequestHeader as FromMap>::from(&map).unwrap();
461 assert_eq!(header.producer_group, "test_producer_group");
462 assert_eq!(header.topic, "test_topic");
463 assert_eq!(header.default_topic, "test_default_topic");
464 assert_eq!(header.default_topic_queue_nums, 8);
465 assert_eq!(header.queue_id, 1);
466 assert_eq!(header.sys_flag, 0);
467 assert_eq!(header.born_timestamp, 1622547800000);
468 assert_eq!(header.flag, 0);
469 assert_eq!(header.properties.unwrap(), "test_properties");
470 assert_eq!(header.reconsume_times.unwrap(), 3);
471 assert!(header.unit_mode.unwrap());
472 assert!(!header.batch.unwrap());
473 assert_eq!(header.max_reconsume_times.unwrap(), 5);
474 }
475
476 #[test]
477 fn send_message_request_header_handles_missing_optional_fields() {
478 let mut map = HashMap::new();
479 map.insert(
480 CheetahString::from_static_str("queueId"),
481 CheetahString::from_static_str("1"),
482 );
483 map.insert(
484 CheetahString::from_static_str("producerGroup"),
485 CheetahString::from_static_str("test_producer_group"),
486 );
487 map.insert(
488 CheetahString::from_static_str("topic"),
489 CheetahString::from_static_str("test_topic"),
490 );
491 map.insert(
492 CheetahString::from_static_str("defaultTopic"),
493 CheetahString::from_static_str("test_default_topic"),
494 );
495 map.insert(
496 CheetahString::from_static_str("defaultTopicQueueNums"),
497 CheetahString::from_static_str("8"),
498 );
499 map.insert(
500 CheetahString::from_static_str("sysFlag"),
501 CheetahString::from_static_str("0"),
502 );
503 map.insert(
504 CheetahString::from_static_str("bornTimestamp"),
505 CheetahString::from_static_str("1622547800000"),
506 );
507 map.insert(
508 CheetahString::from_static_str("flag"),
509 CheetahString::from_static_str("0"),
510 );
511
512 let header = <SendMessageRequestHeader as FromMap>::from(&map).unwrap();
513 assert_eq!(header.producer_group, "test_producer_group");
514 assert_eq!(header.topic, "test_topic");
515 assert_eq!(header.default_topic, "test_default_topic");
516 assert_eq!(header.default_topic_queue_nums, 8);
517 assert_eq!(header.sys_flag, 0);
519 assert_eq!(header.born_timestamp, 1622547800000);
520 assert_eq!(header.flag, 0);
521 assert!(header.properties.is_none());
522 assert!(header.reconsume_times.is_none());
523 assert!(header.unit_mode.is_none());
524 assert!(header.batch.is_none());
525 assert!(header.max_reconsume_times.is_none());
526 }
527
528 #[test]
529 fn send_message_request_header_handles_invalid_data() {
530 let mut map = HashMap::new();
531 map.insert(
532 CheetahString::from_static_str("producerGroup"),
533 CheetahString::from_static_str("test_producer_group"),
534 );
535 map.insert(
536 CheetahString::from_static_str("topic"),
537 CheetahString::from_static_str("test_topic"),
538 );
539 map.insert(
540 CheetahString::from_static_str("defaultTopic"),
541 CheetahString::from_static_str("test_default_topic"),
542 );
543 map.insert(
544 CheetahString::from_static_str("defaultTopicQueueNums"),
545 CheetahString::from_static_str("invalid"),
546 );
547 map.insert(
548 CheetahString::from_static_str("sysFlag"),
549 CheetahString::from_static_str("invalid"),
550 );
551 map.insert(
552 CheetahString::from_static_str("bornTimestamp"),
553 CheetahString::from_static_str("invalid"),
554 );
555 map.insert(
556 CheetahString::from_static_str("flag"),
557 CheetahString::from_static_str("invalid"),
558 );
559
560 let result = <SendMessageRequestHeader as FromMap>::from(&map);
561 assert!(result.is_err());
562 }
563}