1use std::collections::HashMap;
16use std::vec::Vec;
17
18use crate::common::wire_constants::KEY_SEPARATOR;
19use crate::common::wire_constants::RETRY_GROUP_TOPIC_PREFIX;
20use rocketmq_error::Error;
21use rocketmq_error::Result;
22use rocketmq_model::common::key_builder;
23use rocketmq_model::common::key_builder::KeyBuilder;
24use rocketmq_model::common::pop_retry_policy::PopRetryPolicy;
25
26pub struct ExtraInfoUtil;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct AckExtraInfo<'a> {
30 pub ck_queue_offset: i64,
31 pub pop_time: i64,
32 pub invisible_time: i64,
33 pub revive_queue_id: i32,
34 pub retry: &'a str,
35 pub broker_name: &'a str,
36 pub queue_id: i32,
37 pub queue_offset: i64,
38 pub retry_generation: Option<u64>,
39}
40
41const NORMAL_TOPIC: &str = "0";
42const RETRY_TOPIC: &str = "1";
43const RETRY_TOPIC_V2: &str = "2";
44const QUEUE_OFFSET: &str = "qo";
45
46#[inline]
47fn illegal_argument(message: impl Into<String>) -> Error {
48 crate::error::invalid_argument(message)
49}
50
51impl ExtraInfoUtil {
52 pub fn parse_ack_extra_info(extra_info: &str) -> Result<AckExtraInfo<'_>> {
54 let mut parts = extra_info.split(KEY_SEPARATOR);
55 let mut next = |name: &str| {
56 parts
57 .next()
58 .ok_or_else(|| illegal_argument(format!("parse POP receipt handle: missing {name}")))
59 };
60
61 let ck_queue_offset = next("checkpoint queue offset")?
62 .parse::<i64>()
63 .map_err(|_| illegal_argument("parse ck_queue_offset error"))?;
64 let pop_time = next("pop time")?
65 .parse::<i64>()
66 .map_err(|_| illegal_argument("parse pop_time error"))?;
67 let invisible_time = next("invisible time")?
68 .parse::<i64>()
69 .map_err(|_| illegal_argument("parse invisible_time error"))?;
70 let revive_queue_id = next("revive queue id")?
71 .parse::<i32>()
72 .map_err(|_| illegal_argument("parse revive_qid error"))?;
73 let retry = next("retry marker")?;
74 let broker_name = next("broker name")?;
75 let queue_id = next("queue id")?
76 .parse::<i32>()
77 .map_err(|_| illegal_argument("parse queue_id error"))?;
78 let queue_offset = next("queue offset")?
79 .parse::<i64>()
80 .map_err(|_| illegal_argument("parse queue_offset error"))?;
81 let retry_generation = parts
82 .next()
83 .map(|value| {
84 value
85 .parse::<u64>()
86 .map_err(|_| illegal_argument("parse retry generation error"))
87 })
88 .transpose()?;
89
90 if broker_name.trim().is_empty() {
91 return Err(illegal_argument("parse POP receipt handle: broker name is blank"));
92 }
93 if !matches!(retry, NORMAL_TOPIC | RETRY_TOPIC | RETRY_TOPIC_V2) {
94 return Err(illegal_argument("parse POP receipt handle: invalid retry marker"));
95 }
96
97 Ok(AckExtraInfo {
98 ck_queue_offset,
99 pop_time,
100 invisible_time,
101 revive_queue_id,
102 retry,
103 broker_name,
104 queue_id,
105 queue_offset,
106 retry_generation,
107 })
108 }
109
110 pub fn split(extra_info: &str) -> Vec<String> {
112 extra_info.split(KEY_SEPARATOR).map(|s| s.to_string()).collect()
113 }
114
115 pub fn get_ck_queue_offset(extra_info_strs: &[String]) -> Result<i64> {
117 if extra_info_strs.is_empty() {
118 return Err(illegal_argument(format!(
119 "getCkQueueOffset fail, extraInfoStrs length {}",
120 extra_info_strs.len()
121 )));
122 }
123 extra_info_strs[0]
124 .parse::<i64>()
125 .map_err(|_| illegal_argument("parse ck_queue_offset error"))
126 }
127
128 pub fn get_pop_time(extra_info_strs: &[String]) -> Result<i64> {
130 if extra_info_strs.len() < 2 {
131 return Err(illegal_argument(format!(
132 "getPopTime fail, extraInfoStrs length {}",
133 extra_info_strs.len()
134 )));
135 }
136 extra_info_strs[1]
137 .parse::<i64>()
138 .map_err(|_| illegal_argument("parse pop_time error"))
139 }
140
141 pub fn get_invisible_time(extra_info_strs: &[String]) -> Result<i64> {
143 if extra_info_strs.len() < 3 {
144 return Err(illegal_argument(format!(
145 "getInvisibleTime fail, extraInfoStrs length {}",
146 extra_info_strs.len()
147 )));
148 }
149 extra_info_strs[2]
150 .parse::<i64>()
151 .map_err(|_| illegal_argument("parse invisible_time error"))
152 }
153
154 pub fn get_revive_qid(extra_info_strs: &[String]) -> Result<i32> {
156 if extra_info_strs.len() < 4 {
157 return Err(illegal_argument(format!(
158 "getReviveQid fail, extraInfoStrs length {}",
159 extra_info_strs.len()
160 )));
161 }
162 extra_info_strs[3]
163 .parse::<i32>()
164 .map_err(|_| illegal_argument("parse revive_qid error"))
165 }
166
167 pub fn get_real_topic(extra_info_strs: &[String], topic: &str, cid: &str) -> Result<String> {
169 if extra_info_strs.len() < 5 {
170 return Err(illegal_argument(format!(
171 "getRealTopic fail, extraInfoStrs length {}",
172 extra_info_strs.len()
173 )));
174 }
175
176 if RETRY_TOPIC == extra_info_strs[4] {
177 Ok(KeyBuilder::build_pop_retry_topic_v1(topic, cid))
178 } else if RETRY_TOPIC_V2 == extra_info_strs[4] {
179 Ok(KeyBuilder::build_pop_retry_topic_v2(topic, cid))
180 } else {
181 Ok(topic.to_string())
182 }
183 }
184
185 pub fn get_real_topic_with_retry(topic: &str, cid: &str, retry: &str) -> Result<String> {
187 if retry == NORMAL_TOPIC {
188 Ok(topic.to_string())
189 } else if retry == RETRY_TOPIC {
190 Ok(KeyBuilder::build_pop_retry_topic_v1(topic, cid))
191 } else if retry == RETRY_TOPIC_V2 {
192 Ok(KeyBuilder::build_pop_retry_topic_v2(topic, cid))
193 } else {
194 Err(illegal_argument("getRetry fail, format is wrong"))
195 }
196 }
197
198 pub fn get_retry(extra_info_strs: &[String]) -> Result<String> {
200 if extra_info_strs.len() < 5 {
201 return Err(illegal_argument(format!(
202 "getRetry fail, extraInfoStrs length {}",
203 extra_info_strs.len()
204 )));
205 }
206 Ok(extra_info_strs[4].clone())
207 }
208
209 pub fn get_broker_name(extra_info_strs: &[String]) -> Result<String> {
211 if extra_info_strs.len() < 6 {
212 return Err(illegal_argument(format!(
213 "getBrokerName fail, extraInfoStrs length {}",
214 extra_info_strs.len()
215 )));
216 }
217 Ok(extra_info_strs[5].clone())
218 }
219
220 pub fn get_queue_id(extra_info_strs: &[String]) -> rocketmq_error::Result<i32> {
222 if extra_info_strs.len() < 7 {
223 return Err(illegal_argument(format!(
224 "getQueueId fail, extraInfoStrs length {}",
225 extra_info_strs.len()
226 )));
227 }
228 extra_info_strs[6]
229 .parse::<i32>()
230 .map_err(|_| illegal_argument("parse queue_id error"))
231 }
232
233 pub fn get_queue_offset(extra_info_strs: &[String]) -> rocketmq_error::Result<i64> {
235 if extra_info_strs.len() < 8 {
236 return Err(illegal_argument(format!(
237 "getQueueOffset fail, extraInfoStrs length {}",
238 extra_info_strs.len()
239 )));
240 }
241 extra_info_strs[7]
242 .parse::<i64>()
243 .map_err(|_| illegal_argument("parse queue_offset error"))
244 }
245
246 pub fn build_extra_info(
248 ck_queue_offset: i64,
249 pop_time: i64,
250 invisible_time: i64,
251 revive_qid: i32,
252 topic: &str,
253 broker_name: &str,
254 queue_id: i32,
255 ) -> String {
256 let t = Self::get_retry_from_topic(topic);
257 format!(
258 "{}{sep}{}{sep}{}{sep}{}{sep}{}{sep}{}{sep}{}",
259 ck_queue_offset,
260 pop_time,
261 invisible_time,
262 revive_qid,
263 t,
264 broker_name,
265 queue_id,
266 sep = KEY_SEPARATOR
267 )
268 }
269
270 pub fn build_extra_info_with_offset(
272 ck_queue_offset: i64,
273 pop_time: i64,
274 invisible_time: i64,
275 revive_qid: i32,
276 topic: &str,
277 broker_name: &str,
278 queue_id: i32,
279 msg_queue_offset: i64,
280 ) -> String {
281 let t = Self::get_retry_from_topic(topic);
282 format!(
283 "{}{sep}{}{sep}{}{sep}{}{sep}{}{sep}{}{sep}{}{sep}{}",
284 ck_queue_offset,
285 pop_time,
286 invisible_time,
287 revive_qid,
288 t,
289 broker_name,
290 queue_id,
291 msg_queue_offset,
292 sep = KEY_SEPARATOR
293 )
294 }
295
296 #[allow(clippy::too_many_arguments, reason = "preserves the established POP receipt fields")]
301 pub fn build_extra_info_with_offset_and_retry_policy(
302 ck_queue_offset: i64,
303 pop_time: i64,
304 invisible_time: i64,
305 revive_qid: i32,
306 topic: &str,
307 broker_name: &str,
308 queue_id: i32,
309 msg_queue_offset: i64,
310 retry_policy: &PopRetryPolicy,
311 ) -> String {
312 let base = Self::build_extra_info_with_offset(
313 ck_queue_offset,
314 pop_time,
315 invisible_time,
316 revive_qid,
317 topic,
318 broker_name,
319 queue_id,
320 msg_queue_offset,
321 );
322 if KeyBuilder::classify_pop_retry_topic(topic).is_some() {
323 format!("{base}{KEY_SEPARATOR}{}", retry_policy.generation)
324 } else {
325 base
326 }
327 }
328
329 pub fn build_start_offset_info(string_builder: &mut String, topic: &str, queue_id: i32, start_offset: i64) {
331 if !string_builder.is_empty() {
332 string_builder.push(';');
333 }
334
335 string_builder.push_str(&Self::get_retry_from_topic(topic));
336 string_builder.push_str(KEY_SEPARATOR);
337 string_builder.push_str(&queue_id.to_string());
338 string_builder.push_str(KEY_SEPARATOR);
339 string_builder.push_str(&start_offset.to_string());
340 }
341
342 pub fn build_queue_id_order_count_info(string_builder: &mut String, topic: &str, queue_id: i32, order_count: i32) {
344 if !string_builder.is_empty() {
345 string_builder.push(';');
346 }
347
348 string_builder.push_str(&Self::get_retry_from_topic(topic));
349 string_builder.push_str(KEY_SEPARATOR);
350 string_builder.push_str(&queue_id.to_string());
351 string_builder.push_str(KEY_SEPARATOR);
352 string_builder.push_str(&order_count.to_string());
353 }
354
355 pub fn build_queue_offset_order_count_info(
357 string_builder: &mut String,
358 topic: &str,
359 queue_id: i64,
360 queue_offset: i64,
361 order_count: i32,
362 ) {
363 if !string_builder.is_empty() {
364 string_builder.push(';');
365 }
366
367 string_builder.push_str(&Self::get_retry_from_topic(topic));
368 string_builder.push_str(KEY_SEPARATOR);
369 string_builder.push_str(&Self::get_queue_offset_key_value_key(queue_id, queue_offset));
370 string_builder.push_str(KEY_SEPARATOR);
371 string_builder.push_str(&order_count.to_string());
372 }
373
374 pub fn build_msg_offset_info(string_builder: &mut String, topic: &str, queue_id: i32, msg_offsets: &[u64]) {
376 if !string_builder.is_empty() {
377 string_builder.push(';');
378 }
379
380 string_builder.push_str(&Self::get_retry_from_topic(topic));
381 string_builder.push_str(KEY_SEPARATOR);
382 string_builder.push_str(&queue_id.to_string());
383 string_builder.push_str(KEY_SEPARATOR);
384
385 for (i, offset) in msg_offsets.iter().enumerate() {
386 string_builder.push_str(&offset.to_string());
387 if i < msg_offsets.len() - 1 {
388 string_builder.push(';');
389 }
390 }
391 }
392
393 pub fn parse_msg_offset_info(msg_offset_info: &str) -> Result<HashMap<String, Vec<i64>>> {
395 let mut msg_offset_map: HashMap<String, Vec<i64>> = HashMap::with_capacity(4);
396 if msg_offset_info.is_empty() {
397 return Ok(msg_offset_map);
398 }
399 let array = if !msg_offset_info.contains(";") {
400 vec![msg_offset_info]
401 } else {
402 msg_offset_info.split(";").collect::<Vec<&str>>()
403 };
404
405 for one in array {
406 let split: Vec<&str> = one.split(KEY_SEPARATOR).collect();
407 if split.len() != 3 {
408 return Err(illegal_argument("parse msgOffsetInfo error"));
409 }
410
411 let key = format!("{}@{}", split[0], split[1]);
412 let offsets = match msg_offset_map.entry(key) {
413 std::collections::hash_map::Entry::Occupied(_) => {
414 return Err(illegal_argument("parse msgOffsetMap error, duplicate"));
415 }
416 std::collections::hash_map::Entry::Vacant(entry) => entry.insert(Vec::with_capacity(8)),
417 };
418 let msg_offsets: Vec<&str> = split[2].split(",").collect();
419
420 for msg_offset in msg_offsets {
421 let offset = msg_offset
422 .parse::<i64>()
423 .map_err(|_| illegal_argument("parse msgOffset error"))?;
424 offsets.push(offset);
425 }
426 }
427
428 Ok(msg_offset_map)
429 }
430
431 pub fn parse_start_offset_info(start_offset_info: &str) -> Result<HashMap<String, i64>> {
433 let mut start_offset_map: HashMap<String, i64> = HashMap::with_capacity(4);
434 if start_offset_info.is_empty() {
435 return Ok(start_offset_map);
436 }
437 let array = if !start_offset_info.contains(";") {
438 vec![start_offset_info]
439 } else {
440 start_offset_info.split(";").collect::<Vec<&str>>()
441 };
442
443 for one in array {
444 let split: Vec<&str> = one.split(KEY_SEPARATOR).collect();
445 if split.len() != 3 {
446 return Err(illegal_argument(format!(
447 "parse startOffsetInfo error, {start_offset_info}"
448 )));
449 }
450
451 let key = format!("{}@{}", split[0], split[1]);
452 if start_offset_map.contains_key(&key) {
453 return Err(illegal_argument("parse startOffsetMap error, duplicate"));
454 }
455
456 start_offset_map.insert(
457 key,
458 split[2]
459 .parse::<i64>()
460 .map_err(|_| illegal_argument("parse startOffset error"))?,
461 );
462 }
463
464 Ok(start_offset_map)
465 }
466
467 pub fn parse_order_count_info(order_count_info: &str) -> Result<HashMap<String, i32>> {
469 let mut order_count_map: HashMap<String, i32> = HashMap::with_capacity(4);
470 if order_count_info.is_empty() {
471 return Ok(order_count_map);
472 }
473 let array = if !order_count_info.contains(";") {
474 vec![order_count_info]
475 } else {
476 order_count_info.split(";").collect::<Vec<&str>>()
477 };
478
479 for one in array {
480 let split: Vec<&str> = one.split(KEY_SEPARATOR).collect();
481 if split.len() != 3 {
482 return Err(illegal_argument(format!(
483 "parse orderCountInfo error {order_count_info}"
484 )));
485 }
486
487 let key = format!("{}@{}", split[0], split[1]);
488 if order_count_map.contains_key(&key) {
489 return Err(illegal_argument(format!(
490 "parse orderCountInfo error, duplicate, {order_count_info}"
491 )));
492 }
493
494 order_count_map.insert(
495 key,
496 split[2]
497 .parse::<i32>()
498 .map_err(|_| illegal_argument("parse orderCountInfo error"))?,
499 );
500 }
501
502 Ok(order_count_map)
503 }
504
505 pub fn get_start_offset_info_map_key(topic: &str, key: i64) -> String {
507 format!("{}@{}", Self::get_retry_from_topic(topic), key)
508 }
509
510 pub fn get_start_offset_info_map_key_with_pop_ck(topic: &str, pop_ck: Option<&str>, key: i64) -> Result<String> {
512 Ok(format!("{}@{}", Self::get_retry_from_topic_pop_ck(topic, pop_ck)?, key))
513 }
514
515 pub fn get_queue_offset_key_value_key(queue_id: i64, queue_offset: i64) -> String {
517 format!("{}{}{}{}", QUEUE_OFFSET, queue_id, "%", queue_offset)
518 }
519
520 pub fn get_queue_offset_map_key(topic: &str, queue_id: i64, queue_offset: i64) -> String {
522 format!(
523 "{}@{}",
524 Self::get_retry_from_topic(topic),
525 Self::get_queue_offset_key_value_key(queue_id, queue_offset)
526 )
527 }
528
529 pub fn is_order(extra_info: &[String]) -> bool {
531 Self::get_revive_qid(extra_info).unwrap_or_default() == key_builder::POP_ORDER_REVIVE_QUEUE
532 }
533
534 fn get_retry_from_topic(topic: &str) -> String {
536 if KeyBuilder::is_pop_retry_topic_v2(topic) {
537 RETRY_TOPIC_V2.to_string()
538 } else if topic.starts_with(RETRY_GROUP_TOPIC_PREFIX) {
539 RETRY_TOPIC.to_string()
540 } else {
541 NORMAL_TOPIC.to_string()
542 }
543 }
544
545 fn get_retry_from_topic_pop_ck(topic: &str, pop_ck: Option<&str>) -> Result<String> {
547 match pop_ck {
548 Some(ck) => Self::get_retry(&Self::split(ck)),
549 None => Ok(Self::get_retry_from_topic(topic)),
550 }
551 }
552}
553
554#[cfg(test)]
555mod tests {
556 use rocketmq_model::common::key_builder::KeyBuilder;
557
558 use super::*;
559
560 #[test]
561 fn split_with_valid_string() {
562 let result = ExtraInfoUtil::split("a b c");
563 assert_eq!(result, vec!["a", "b", "c"]);
564 }
565
566 #[test]
567 fn get_ck_queue_offset_with_valid_string() {
568 let result = ExtraInfoUtil::get_ck_queue_offset(&["123".to_string()]).unwrap();
569 assert_eq!(result, 123);
570 }
571
572 #[test]
573 fn get_ck_queue_offset_with_invalid_string() {
574 let result = ExtraInfoUtil::get_ck_queue_offset(&["abc".to_string()]).unwrap_err();
575 assert_eq!(result.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
576 }
577
578 #[test]
579 fn get_pop_time_with_valid_string() {
580 let result = ExtraInfoUtil::get_pop_time(&["123".to_string(), "456".to_string()]).unwrap();
581 assert_eq!(result, 456);
582 }
583
584 #[test]
585 fn get_pop_time_with_insufficient_length() {
586 let result = ExtraInfoUtil::get_pop_time(&["123".to_string()]).unwrap_err();
587 assert_eq!(result.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
588 }
589
590 #[test]
591 fn get_invisible_time_with_valid_string() {
592 let result =
593 ExtraInfoUtil::get_invisible_time(&["123".to_string(), "456".to_string(), "789".to_string()]).unwrap();
594 assert_eq!(result, 789);
595 }
596
597 #[test]
598 fn get_invisible_time_with_insufficient_length() {
599 let result = ExtraInfoUtil::get_invisible_time(&["123".to_string(), "456".to_string()]).unwrap_err();
600 assert_eq!(result.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
601 }
602
603 #[test]
604 fn get_revive_qid_with_valid_string() {
605 let result = ExtraInfoUtil::get_revive_qid(&[
606 "123".to_string(),
607 "456".to_string(),
608 "789".to_string(),
609 "10".to_string(),
610 ])
611 .unwrap();
612 assert_eq!(result, 10);
613 }
614
615 #[test]
616 fn get_revive_qid_with_insufficient_length() {
617 let result =
618 ExtraInfoUtil::get_revive_qid(&["123".to_string(), "456".to_string(), "789".to_string()]).unwrap_err();
619 assert_eq!(result.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
620 }
621
622 #[test]
623 fn get_real_topic_with_retry_v1() {
624 let result = ExtraInfoUtil::get_real_topic(
625 &[
626 "123".to_string(),
627 "456".to_string(),
628 "789".to_string(),
629 "10".to_string(),
630 "1".to_string(),
631 ],
632 "topic",
633 "cid",
634 )
635 .unwrap();
636 assert_eq!(result, KeyBuilder::build_pop_retry_topic_v1("topic", "cid"));
637 }
638
639 #[test]
640 fn get_real_topic_with_retry_v2() {
641 let result = ExtraInfoUtil::get_real_topic(
642 &[
643 "123".to_string(),
644 "456".to_string(),
645 "789".to_string(),
646 "10".to_string(),
647 "2".to_string(),
648 ],
649 "topic",
650 "cid",
651 )
652 .unwrap();
653 assert_eq!(result, KeyBuilder::build_pop_retry_topic_v2("topic", "cid"));
654 }
655
656 #[test]
657 fn get_real_topic_with_normal_topic() {
658 let result = ExtraInfoUtil::get_real_topic(
659 &[
660 "123".to_string(),
661 "456".to_string(),
662 "789".to_string(),
663 "10".to_string(),
664 "0".to_string(),
665 ],
666 "topic",
667 "cid",
668 )
669 .unwrap();
670 assert_eq!(result, "topic".to_string());
671 }
672
673 #[test]
674 fn get_real_topic_with_insufficient_length() {
675 let result = ExtraInfoUtil::get_real_topic(
676 &[
677 "123".to_string(),
678 "456".to_string(),
679 "789".to_string(),
680 "10".to_string(),
681 ],
682 "topic",
683 "cid",
684 )
685 .unwrap_err();
686 assert_eq!(result.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
687 }
688
689 #[test]
690 fn get_real_topic_with_retry_invalid() {
691 let result = ExtraInfoUtil::get_real_topic_with_retry("topic", "cid", "3").unwrap_err();
692 assert_eq!(result.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
693 }
694
695 #[test]
696 fn get_retry_slice_with_valid_string() {
697 let result = ExtraInfoUtil::get_retry(&[
698 "123".to_string(),
699 "456".to_string(),
700 "789".to_string(),
701 "10".to_string(),
702 "1".to_string(),
703 ])
704 .unwrap();
705 assert_eq!(result, "1".to_string());
706 }
707
708 #[test]
709 fn get_retry_slice_with_insufficient_length() {
710 let result = ExtraInfoUtil::get_retry(&[
711 "123".to_string(),
712 "456".to_string(),
713 "789".to_string(),
714 "10".to_string(),
715 ])
716 .unwrap_err();
717 assert_eq!(result.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
718 }
719
720 #[test]
721 fn get_broker_name_with_valid_string() {
722 let result = ExtraInfoUtil::get_broker_name(&[
723 "123".to_string(),
724 "456".to_string(),
725 "789".to_string(),
726 "10".to_string(),
727 "1".to_string(),
728 "broker".to_string(),
729 ])
730 .unwrap();
731 assert_eq!(result, "broker".to_string());
732 }
733
734 #[test]
735 fn get_broker_name_with_insufficient_length() {
736 let result = ExtraInfoUtil::get_broker_name(&[
737 "123".to_string(),
738 "456".to_string(),
739 "789".to_string(),
740 "10".to_string(),
741 "1".to_string(),
742 ])
743 .unwrap_err();
744 assert_eq!(result.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
745 }
746
747 #[test]
748 fn get_queue_id_with_valid_string() {
749 let result = ExtraInfoUtil::get_queue_id(&[
750 "123".to_string(),
751 "456".to_string(),
752 "789".to_string(),
753 "10".to_string(),
754 "1".to_string(),
755 "broker".to_string(),
756 "7".to_string(),
757 ])
758 .unwrap();
759 assert_eq!(result, 7);
760 }
761
762 #[test]
763 fn get_queue_id_with_insufficient_length() {
764 let result = ExtraInfoUtil::get_queue_id(&[
765 "123".to_string(),
766 "456".to_string(),
767 "789".to_string(),
768 "10".to_string(),
769 "1".to_string(),
770 "broker".to_string(),
771 ])
772 .unwrap_err();
773 assert_eq!(result.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
774 }
775
776 #[test]
777 fn get_queue_offset_with_valid_string() {
778 let result = ExtraInfoUtil::get_queue_offset(&[
779 "123".to_string(),
780 "456".to_string(),
781 "789".to_string(),
782 "10".to_string(),
783 "1".to_string(),
784 "broker".to_string(),
785 "7".to_string(),
786 "100".to_string(),
787 ])
788 .unwrap();
789 assert_eq!(result, 100);
790 }
791
792 #[test]
793 fn get_queue_offset_with_insufficient_length() {
794 let result = ExtraInfoUtil::get_queue_offset(&[
795 "123".to_string(),
796 "456".to_string(),
797 "789".to_string(),
798 "10".to_string(),
799 "1".to_string(),
800 "broker".to_string(),
801 "7".to_string(),
802 ])
803 .unwrap_err();
804 assert_eq!(result.descriptor(), &rocketmq_error::CORE_ARGUMENT_INVALID);
805 }
806
807 #[test]
808 fn build_extra_info_creates_correct_string() {
809 let result = ExtraInfoUtil::build_extra_info(123, 456, 789, 10, "topic", "broker", 7);
810 assert_eq!(result, "123 456 789 10 0 broker 7");
811 }
812
813 #[test]
814 fn build_extra_info_with_msg_queue_offset_creates_correct_string() {
815 let result = ExtraInfoUtil::build_extra_info_with_offset(123, 456, 789, 10, "topic", "broker", 7, 100);
816 assert_eq!(result, "123 456 789 10 0 broker 7 100");
817 }
818
819 #[test]
820 fn retry_receipt_round_trips_actual_version_and_policy_generation() {
821 let policy = PopRetryPolicy::dual_read_v2_write(17);
822 let retry_topic = policy.write_topic("topic", "consumer-a");
823 let handle = ExtraInfoUtil::build_extra_info_with_offset_and_retry_policy(
824 123,
825 456,
826 789,
827 10,
828 &retry_topic,
829 "broker",
830 7,
831 100,
832 &policy,
833 );
834
835 let parsed = ExtraInfoUtil::parse_ack_extra_info(&handle).expect("generated receipt should parse");
836 assert_eq!(parsed.retry, RETRY_TOPIC_V2);
837 assert_eq!(parsed.retry_generation, Some(17));
838 assert_eq!(
839 ExtraInfoUtil::get_real_topic_with_retry("topic", "consumer-a", parsed.retry)
840 .expect("retry marker should resolve"),
841 retry_topic
842 );
843 }
844
845 #[test]
846 fn build_start_offset_info_creates_correct_string() {
847 let mut string_builder = String::new();
848 ExtraInfoUtil::build_start_offset_info(&mut string_builder, "topic", 7, 100);
849 assert_eq!(string_builder, "0 7 100");
850 }
851
852 #[test]
853 fn build_queue_id_order_count_info_creates_correct_string() {
854 let mut string_builder = String::new();
855 ExtraInfoUtil::build_queue_id_order_count_info(&mut string_builder, "topic", 7, 100);
856 assert_eq!(string_builder, "0 7 100");
857 }
858
859 #[test]
860 fn build_queue_offset_order_count_info_creates_correct_string() {
861 let mut string_builder = String::new();
862 ExtraInfoUtil::build_queue_offset_order_count_info(&mut string_builder, "topic", 7, 100, 100);
863 assert_eq!(string_builder, "0 qo7%100 100");
864 }
865
866 #[test]
867 fn build_msg_offset_info_creates_correct_string() {
868 let mut string_builder = String::new();
869 ExtraInfoUtil::build_msg_offset_info(&mut string_builder, "topic", 7, &[100, 200, 300]);
870 assert_eq!(string_builder, "0 7 100;200;300");
871 }
872
873 #[test]
874 fn parse_msg_offset_info_with_valid_string() {
875 let result = ExtraInfoUtil::parse_msg_offset_info("0 7 100,200,300").unwrap();
876 let mut expected = HashMap::new();
877 expected.insert("0@7".to_string(), vec![100, 200, 300]);
878 assert_eq!(result, expected);
879 }
880
881 #[test]
882 fn parse_start_offset_info_with_valid_string() {
883 let result = ExtraInfoUtil::parse_start_offset_info("0 7 100").unwrap();
884 let mut expected = HashMap::new();
885 expected.insert("0@7".to_string(), 100);
886 assert_eq!(result, expected);
887 }
888
889 #[test]
890 fn parse_order_count_info_with_valid_string() {
891 let result = ExtraInfoUtil::parse_order_count_info("0 7 100").unwrap();
892 let mut expected = HashMap::new();
893 expected.insert("0@7".to_string(), 100);
894 assert_eq!(result, expected);
895 }
896
897 #[test]
898 fn get_retry_for_topic_with_retry_topic() {
899 let result = ExtraInfoUtil::get_retry_from_topic("RETRY_topic");
900 assert_eq!(result, "0");
901 }
902
903 #[test]
904 fn get_retry_for_topic_with_normal_topic() {
905 let result = ExtraInfoUtil::get_retry_from_topic("topic");
906 assert_eq!(result, "0");
907 }
908
909 #[test]
910 fn get_start_offset_info_map_key_creates_correct_string() {
911 let result = ExtraInfoUtil::get_start_offset_info_map_key("topic", 7);
912 assert_eq!(result, "0@7");
913 }
914
915 #[test]
916 fn get_start_offset_info_map_key_with_pop_ck_creates_correct_string() {
917 let result = ExtraInfoUtil::get_start_offset_info_map_key_with_pop_ck("topic", None, 7).unwrap();
918 assert_eq!(result, "0@7");
919 }
920
921 #[test]
922 fn get_queue_offset_key_value_key_creates_correct_string() {
923 let result = ExtraInfoUtil::get_queue_offset_key_value_key(7, 100);
924 assert_eq!(result, "qo7%100");
925 }
926
927 #[test]
928 fn get_queue_offset_map_key_creates_correct_string() {
929 let result = ExtraInfoUtil::get_queue_offset_map_key("topic", 7, 100);
930 assert_eq!(result, "0@qo7%100");
931 }
932
933 #[test]
934 fn is_order_with_order_queue() {
935 let result = ExtraInfoUtil::is_order(&[
936 "123".to_string(),
937 "456".to_string(),
938 "789".to_string(),
939 "999".to_string(),
940 "1".to_string(),
941 "broker".to_string(),
942 "7".to_string(),
943 "999".to_string(),
944 "0".to_string(),
945 ]);
946 assert!(result);
947 }
948
949 #[test]
950 fn is_order_with_non_order_queue() {
951 let result = ExtraInfoUtil::is_order(&[
952 "123".to_string(),
953 "456".to_string(),
954 "789".to_string(),
955 "10".to_string(),
956 "1".to_string(),
957 "broker".to_string(),
958 "7".to_string(),
959 "100".to_string(),
960 "1".to_string(),
961 ]);
962 assert!(!result);
963 }
964}