1use cheetah_string::CheetahString;
19use rocketmq_model::common::message::message_enum::MessageType;
20use rocketmq_model::common::message::MessageConst;
21
22use crate::base::access_channel::AccessChannel;
23use crate::producer::local_transaction_state::LocalTransactionState;
24use crate::trace::trace_bean::TraceBean;
25use crate::trace::trace_constants::TraceConstants;
26use crate::trace::trace_context::TraceContext;
27use crate::trace::trace_transfer_bean::TraceTransferBean;
28use crate::trace::trace_type::TraceType;
29
30pub struct TraceDataEncoder;
36
37impl TraceDataEncoder {
38 pub fn decoder_from_trace_data_string(trace_data: &str) -> Vec<TraceContext> {
46 if trace_data.is_empty() {
48 return Vec::new();
49 }
50
51 let estimated_size = trace_data.matches(TraceConstants::FIELD_SPLITOR).count();
53 let mut res_list = Vec::with_capacity(estimated_size.max(1));
54
55 for context_str in trace_data.split(TraceConstants::FIELD_SPLITOR) {
57 if context_str.is_empty() {
58 continue;
59 }
60
61 let line: Vec<&str> = context_str.split(TraceConstants::CONTENT_SPLITOR).collect();
62 if line.is_empty() {
63 continue;
64 }
65
66 match line[0] {
68 "Pub" => {
69 if let Some(ctx) = Self::decode_pub_context(&line) {
70 res_list.push(ctx);
71 }
72 }
73 "SubBefore" => {
74 if let Some(ctx) = Self::decode_sub_before_context(&line) {
75 res_list.push(ctx);
76 }
77 }
78 "SubAfter" => {
79 if let Some(ctx) = Self::decode_sub_after_context(&line) {
80 res_list.push(ctx);
81 }
82 }
83 "EndTransaction" => {
84 if let Some(ctx) = Self::decode_end_transaction_context(&line) {
85 res_list.push(ctx);
86 }
87 }
88 "Recall" => {
89 if let Some(ctx) = Self::decode_recall_context(&line) {
90 res_list.push(ctx);
91 }
92 }
93 _ => {} }
95 }
96
97 res_list
98 }
99
100 pub fn encoder_from_context_bean(ctx: &TraceContext) -> Option<TraceTransferBean> {
108 let trace_beans = ctx.trace_beans.as_ref()?;
109 if trace_beans.is_empty() {
110 return None;
111 }
112
113 let mut transfer_bean = TraceTransferBean::new();
114 let mut sb = String::with_capacity(256);
116
117 match ctx.trace_type? {
118 TraceType::Pub => {
119 Self::encode_pub_context(ctx, &trace_beans[0], &mut sb);
120 }
121 TraceType::SubBefore => {
122 Self::encode_sub_before_context(ctx, trace_beans, &mut sb);
123 }
124 TraceType::SubAfter => {
125 Self::encode_sub_after_context(ctx, trace_beans, &mut sb);
126 }
127 TraceType::EndTransaction => {
128 Self::encode_end_transaction_context(ctx, &trace_beans[0], &mut sb);
129 }
130 TraceType::Recall => {
131 Self::encode_recall_context(ctx, &trace_beans[0], &mut sb);
132 }
133 }
134
135 let records = rocketmq_protocol::trace::decode_records(&sb);
136 transfer_bean.set_trans_data(rocketmq_protocol::trace::encode_records(&records));
137
138 for bean in trace_beans {
140 transfer_bean.add_key(bean.msg_id.clone());
141
142 if !bean.keys.is_empty() {
143 let keys: Vec<&str> = bean.keys.split_str(MessageConst::KEY_SEPARATOR).collect();
144 for key in keys {
145 if !key.is_empty() {
146 transfer_bean.add_key(CheetahString::from_slice(key));
147 }
148 }
149 }
150 }
151
152 Some(transfer_bean)
153 }
154
155 #[inline]
158 fn decode_pub_context(line: &[&str]) -> Option<TraceContext> {
159 if line.len() < 12 {
160 return None;
161 }
162
163 let time_stamp = line[1].parse().ok()?;
164 let region_id = CheetahString::from_slice(line[2]);
165 let group_name = CheetahString::from_slice(line[3]);
166 let cost_time = line[10].parse().ok()?;
167 let body_length = line[9].parse().ok()?;
168
169 let msg_type = if let Ok(msg_type_ordinal) = line[11].parse::<usize>() {
171 Self::message_type_from_ordinal(msg_type_ordinal)
172 } else {
173 None
174 };
175
176 let (is_success, offset_msg_id, client_host) = if line.len() == 13 {
178 (
179 line[12].parse().unwrap_or(true),
180 CheetahString::default(),
181 CheetahString::default(),
182 )
183 } else if line.len() == 14 {
184 (
185 line[13].parse().unwrap_or(true),
186 CheetahString::from_slice(line[12]),
187 CheetahString::default(),
188 )
189 } else if line.len() >= 15 {
190 (
191 line[13].parse().unwrap_or(true),
192 CheetahString::from_slice(line[12]),
193 CheetahString::from_slice(line[14]),
194 )
195 } else {
196 (true, CheetahString::default(), CheetahString::default())
197 };
198
199 let bean = TraceBean {
200 topic: CheetahString::from_slice(line[4]),
201 msg_id: CheetahString::from_slice(line[5]),
202 tags: CheetahString::from_slice(line[6]),
203 keys: CheetahString::from_slice(line[7]),
204 store_host: CheetahString::from_slice(line[8]),
205 body_length,
206 msg_type,
207 offset_msg_id,
208 client_host,
209 ..Default::default()
210 };
211
212 let ctx = TraceContext {
213 trace_type: Some(TraceType::Pub),
214 time_stamp,
215 region_id,
216 group_name,
217 cost_time,
218 is_success,
219 trace_beans: Some(vec![bean]),
220 ..Default::default()
221 };
222
223 Some(ctx)
224 }
225
226 #[inline]
227 fn decode_sub_before_context(line: &[&str]) -> Option<TraceContext> {
228 if line.len() < 8 {
229 return None;
230 }
231
232 let bean = TraceBean {
233 msg_id: CheetahString::from_slice(line[5]),
234 retry_times: line[6].parse().ok()?,
235 keys: CheetahString::from_slice(line[7]),
236 ..Default::default()
237 };
238
239 let ctx = TraceContext {
240 trace_type: Some(TraceType::SubBefore),
241 time_stamp: line[1].parse().ok()?,
242 region_id: CheetahString::from_slice(line[2]),
243 group_name: CheetahString::from_slice(line[3]),
244 request_id: CheetahString::from_slice(line[4]),
245 trace_beans: Some(vec![bean]),
246 ..Default::default()
247 };
248
249 Some(ctx)
250 }
251
252 #[inline]
253 fn decode_sub_after_context(line: &[&str]) -> Option<TraceContext> {
254 if line.len() < 6 {
255 return None;
256 }
257
258 let context_code = if line.len() >= 7 {
259 line[6].parse().unwrap_or(0)
260 } else {
261 0
262 };
263
264 let (time_stamp, group_name) = if line.len() >= 9 {
265 (line[7].parse().unwrap_or(0), CheetahString::from_slice(line[8]))
266 } else {
267 (0, CheetahString::default())
268 };
269
270 let bean = TraceBean {
271 msg_id: CheetahString::from_slice(line[2]),
272 keys: CheetahString::from_slice(line[5]),
273 ..Default::default()
274 };
275
276 let ctx = TraceContext {
277 trace_type: Some(TraceType::SubAfter),
278 request_id: CheetahString::from_slice(line[1]),
279 cost_time: line[3].parse().ok()?,
280 is_success: line[4].parse().unwrap_or(false),
281 context_code,
282 time_stamp,
283 group_name,
284 trace_beans: Some(vec![bean]),
285 ..Default::default()
286 };
287
288 Some(ctx)
289 }
290
291 #[inline]
292 fn decode_end_transaction_context(line: &[&str]) -> Option<TraceContext> {
293 if line.len() < 13 {
294 return None;
295 }
296
297 let msg_type = if let Ok(msg_type_ordinal) = line[9].parse::<usize>() {
298 Self::message_type_from_ordinal(msg_type_ordinal)
299 } else {
300 None
301 };
302
303 let bean = TraceBean {
304 topic: CheetahString::from_slice(line[4]),
305 msg_id: CheetahString::from_slice(line[5]),
306 tags: CheetahString::from_slice(line[6]),
307 keys: CheetahString::from_slice(line[7]),
308 store_host: CheetahString::from_slice(line[8]),
309 msg_type,
310 transaction_id: Some(CheetahString::from_slice(line[10])),
311 transaction_state: Self::parse_transaction_state(line[11]),
312 from_transaction_check: line[12].parse().unwrap_or(false),
313 ..Default::default()
314 };
315
316 let ctx = TraceContext {
317 trace_type: Some(TraceType::EndTransaction),
318 time_stamp: line[1].parse().ok()?,
319 region_id: CheetahString::from_slice(line[2]),
320 group_name: CheetahString::from_slice(line[3]),
321 trace_beans: Some(vec![bean]),
322 ..Default::default()
323 };
324
325 Some(ctx)
326 }
327
328 #[inline]
329 fn decode_recall_context(line: &[&str]) -> Option<TraceContext> {
330 if line.len() < 7 {
331 return None;
332 }
333
334 let bean = TraceBean {
335 topic: CheetahString::from_slice(line[4]),
336 msg_id: CheetahString::from_slice(line[5]),
337 ..Default::default()
338 };
339
340 let ctx = TraceContext {
341 trace_type: Some(TraceType::Recall),
342 time_stamp: line[1].parse().ok()?,
343 region_id: CheetahString::from_slice(line[2]),
344 group_name: CheetahString::from_slice(line[3]),
345 is_success: line[6].parse().unwrap_or(false),
346 trace_beans: Some(vec![bean]),
347 ..Default::default()
348 };
349
350 Some(ctx)
351 }
352
353 #[inline]
356 fn encode_pub_context(ctx: &TraceContext, bean: &TraceBean, sb: &mut String) {
357 sb.push_str("Pub");
358 sb.push(TraceConstants::CONTENT_SPLITOR);
359 sb.push_str(&ctx.time_stamp.to_string());
360 sb.push(TraceConstants::CONTENT_SPLITOR);
361 sb.push_str(&ctx.region_id);
362 sb.push(TraceConstants::CONTENT_SPLITOR);
363 sb.push_str(&ctx.group_name);
364 sb.push(TraceConstants::CONTENT_SPLITOR);
365 sb.push_str(&bean.topic);
366 sb.push(TraceConstants::CONTENT_SPLITOR);
367 sb.push_str(&bean.msg_id);
368 sb.push(TraceConstants::CONTENT_SPLITOR);
369 sb.push_str(&bean.tags);
370 sb.push(TraceConstants::CONTENT_SPLITOR);
371 sb.push_str(&bean.keys);
372 sb.push(TraceConstants::CONTENT_SPLITOR);
373 sb.push_str(&bean.store_host);
374 sb.push(TraceConstants::CONTENT_SPLITOR);
375 sb.push_str(&bean.body_length.to_string());
376 sb.push(TraceConstants::CONTENT_SPLITOR);
377 sb.push_str(&ctx.cost_time.to_string());
378 sb.push(TraceConstants::CONTENT_SPLITOR);
379
380 if let Some(msg_type) = &bean.msg_type {
381 sb.push_str(&Self::message_type_to_ordinal(*msg_type).to_string());
382 } else {
383 sb.push('0');
384 }
385 sb.push(TraceConstants::CONTENT_SPLITOR);
386 sb.push_str(&bean.offset_msg_id);
387 sb.push(TraceConstants::CONTENT_SPLITOR);
388 sb.push_str(if ctx.is_success { "true" } else { "false" });
389 sb.push(TraceConstants::CONTENT_SPLITOR);
390 sb.push_str(&bean.client_host);
391 sb.push(TraceConstants::FIELD_SPLITOR);
392 }
393
394 #[inline]
395 fn encode_sub_before_context(ctx: &TraceContext, beans: &[TraceBean], sb: &mut String) {
396 for bean in beans {
397 sb.push_str("SubBefore");
398 sb.push(TraceConstants::CONTENT_SPLITOR);
399 sb.push_str(&ctx.time_stamp.to_string());
400 sb.push(TraceConstants::CONTENT_SPLITOR);
401 sb.push_str(&ctx.region_id);
402 sb.push(TraceConstants::CONTENT_SPLITOR);
403 sb.push_str(&ctx.group_name);
404 sb.push(TraceConstants::CONTENT_SPLITOR);
405 sb.push_str(&ctx.request_id);
406 sb.push(TraceConstants::CONTENT_SPLITOR);
407 sb.push_str(&bean.msg_id);
408 sb.push(TraceConstants::CONTENT_SPLITOR);
409 sb.push_str(&bean.retry_times.to_string());
410 sb.push(TraceConstants::CONTENT_SPLITOR);
411 sb.push_str(&bean.keys);
412 sb.push(TraceConstants::FIELD_SPLITOR);
413 }
414 }
415
416 #[inline]
417 fn encode_sub_after_context(ctx: &TraceContext, beans: &[TraceBean], sb: &mut String) {
418 for bean in beans {
419 sb.push_str("SubAfter");
420 sb.push(TraceConstants::CONTENT_SPLITOR);
421 sb.push_str(&ctx.request_id);
422 sb.push(TraceConstants::CONTENT_SPLITOR);
423 sb.push_str(&bean.msg_id);
424 sb.push(TraceConstants::CONTENT_SPLITOR);
425 sb.push_str(&ctx.cost_time.to_string());
426 sb.push(TraceConstants::CONTENT_SPLITOR);
427 sb.push_str(if ctx.is_success { "true" } else { "false" });
428 sb.push(TraceConstants::CONTENT_SPLITOR);
429 sb.push_str(&bean.keys);
430 sb.push(TraceConstants::CONTENT_SPLITOR);
431 sb.push_str(&ctx.context_code.to_string());
432 sb.push(TraceConstants::CONTENT_SPLITOR);
433
434 if !matches!(ctx.access_channel, Some(AccessChannel::Cloud)) {
436 sb.push_str(&ctx.time_stamp.to_string());
437 sb.push(TraceConstants::CONTENT_SPLITOR);
438 sb.push_str(&ctx.group_name);
439 }
440 sb.push(TraceConstants::FIELD_SPLITOR);
441 }
442 }
443
444 #[inline]
445 fn encode_end_transaction_context(ctx: &TraceContext, bean: &TraceBean, sb: &mut String) {
446 sb.push_str("EndTransaction");
447 sb.push(TraceConstants::CONTENT_SPLITOR);
448 sb.push_str(&ctx.time_stamp.to_string());
449 sb.push(TraceConstants::CONTENT_SPLITOR);
450 sb.push_str(&ctx.region_id);
451 sb.push(TraceConstants::CONTENT_SPLITOR);
452 sb.push_str(&ctx.group_name);
453 sb.push(TraceConstants::CONTENT_SPLITOR);
454 sb.push_str(&bean.topic);
455 sb.push(TraceConstants::CONTENT_SPLITOR);
456 sb.push_str(&bean.msg_id);
457 sb.push(TraceConstants::CONTENT_SPLITOR);
458 sb.push_str(&bean.tags);
459 sb.push(TraceConstants::CONTENT_SPLITOR);
460 sb.push_str(&bean.keys);
461 sb.push(TraceConstants::CONTENT_SPLITOR);
462 sb.push_str(&bean.store_host);
463 sb.push(TraceConstants::CONTENT_SPLITOR);
464
465 if let Some(msg_type) = &bean.msg_type {
466 sb.push_str(&Self::message_type_to_ordinal(*msg_type).to_string());
467 } else {
468 sb.push('0');
469 }
470 sb.push(TraceConstants::CONTENT_SPLITOR);
471
472 if let Some(ref transaction_id) = bean.transaction_id {
473 sb.push_str(transaction_id);
474 }
475 sb.push(TraceConstants::CONTENT_SPLITOR);
476
477 if let Some(ref state) = bean.transaction_state {
478 sb.push_str(&state.to_string());
479 }
480 sb.push(TraceConstants::CONTENT_SPLITOR);
481 sb.push_str(if bean.from_transaction_check { "true" } else { "false" });
482 sb.push(TraceConstants::FIELD_SPLITOR);
483 }
484
485 #[inline]
486 fn encode_recall_context(ctx: &TraceContext, bean: &TraceBean, sb: &mut String) {
487 sb.push_str("Recall");
488 sb.push(TraceConstants::CONTENT_SPLITOR);
489 sb.push_str(&ctx.time_stamp.to_string());
490 sb.push(TraceConstants::CONTENT_SPLITOR);
491 sb.push_str(&ctx.region_id);
492 sb.push(TraceConstants::CONTENT_SPLITOR);
493 sb.push_str(&ctx.group_name);
494 sb.push(TraceConstants::CONTENT_SPLITOR);
495 sb.push_str(&bean.topic);
496 sb.push(TraceConstants::CONTENT_SPLITOR);
497 sb.push_str(&bean.msg_id);
498 sb.push(TraceConstants::CONTENT_SPLITOR);
499 sb.push_str(if ctx.is_success { "true" } else { "false" });
500 sb.push(TraceConstants::FIELD_SPLITOR);
501 }
502
503 #[inline]
506 fn message_type_from_ordinal(ordinal: usize) -> Option<MessageType> {
507 match ordinal {
508 0 => Some(MessageType::NormalMsg),
509 1 => Some(MessageType::TransMsgHalf),
510 2 => Some(MessageType::TransMsgCommit),
511 3 => Some(MessageType::DelayMsg),
512 4 => Some(MessageType::OrderMsg),
513 _ => None,
514 }
515 }
516
517 #[inline]
518 fn message_type_to_ordinal(msg_type: MessageType) -> usize {
519 match msg_type {
520 MessageType::NormalMsg => 0,
521 MessageType::TransMsgHalf => 1,
522 MessageType::TransMsgCommit => 2,
523 MessageType::DelayMsg => 3,
524 MessageType::OrderMsg => 4,
525 }
526 }
527
528 #[inline]
529 fn parse_transaction_state(state_str: &str) -> Option<LocalTransactionState> {
530 match state_str {
531 "COMMIT_MESSAGE" => Some(LocalTransactionState::CommitMessage),
532 "ROLLBACK_MESSAGE" => Some(LocalTransactionState::RollbackMessage),
533 "UNKNOW" | "UNKNOWN" => Some(LocalTransactionState::Unknown),
534 _ => None,
535 }
536 }
537}
538
539#[cfg(test)]
540mod tests {
541 use cheetah_string::CheetahString;
542 use rocketmq_model::common::message::message_enum::MessageType;
543
544 use crate::base::access_channel::AccessChannel;
545 use crate::producer::local_transaction_state::LocalTransactionState;
546 use crate::trace::trace_bean::TraceBean;
547 use crate::trace::trace_context::TraceContext;
548 use crate::trace::trace_type::TraceType;
549
550 use super::*;
551
552 #[test]
553 fn test_decode_empty_trace_data() {
554 let result = TraceDataEncoder::decoder_from_trace_data_string("");
555 assert!(result.is_empty());
556 }
557
558 #[test]
559 fn test_encode_and_decode_pub_context() {
560 let bean = TraceBean {
561 topic: CheetahString::from("test-topic"),
562 msg_id: CheetahString::from("msg-001"),
563 tags: CheetahString::from("tagA"),
564 keys: CheetahString::from("key1 key2"),
565 store_host: CheetahString::from("127.0.0.1:10911"),
566 body_length: 1024,
567 msg_type: Some(MessageType::NormalMsg),
568 offset_msg_id: CheetahString::from("offset-001"),
569 client_host: CheetahString::from("192.168.1.1:12345"),
570 ..Default::default()
571 };
572
573 let ctx = TraceContext {
574 trace_type: Some(TraceType::Pub),
575 time_stamp: 1234567890,
576 region_id: CheetahString::from("us-east-1"),
577 group_name: CheetahString::from("test-group"),
578 cost_time: 100,
579 is_success: true,
580 trace_beans: Some(vec![bean]),
581 ..Default::default()
582 };
583
584 let transfer_bean = TraceDataEncoder::encoder_from_context_bean(&ctx).unwrap();
585 let canonical_records = rocketmq_protocol::trace::decode_records(&transfer_bean.trans_data);
586 assert_eq!(canonical_records.len(), 1);
587 assert_eq!(
588 rocketmq_protocol::trace::encode_records(&canonical_records),
589 transfer_bean.trans_data
590 );
591 let decoded_contexts = TraceDataEncoder::decoder_from_trace_data_string(&transfer_bean.trans_data);
592
593 assert_eq!(decoded_contexts.len(), 1);
594 let decoded = &decoded_contexts[0];
595 assert_eq!(decoded.trace_type, Some(TraceType::Pub));
596 assert_eq!(decoded.time_stamp, 1234567890);
597 assert_eq!(decoded.region_id, CheetahString::from("us-east-1"));
598 assert_eq!(decoded.group_name, CheetahString::from("test-group"));
599 assert_eq!(decoded.cost_time, 100);
600 assert!(decoded.is_success);
601 assert!(decoded.trace_beans.is_some());
602 let decoded_bean = &decoded.trace_beans.as_ref().unwrap()[0];
603 assert_eq!(decoded_bean.topic, CheetahString::from("test-topic"));
604 assert_eq!(decoded_bean.msg_id, CheetahString::from("msg-001"));
605 assert_eq!(decoded_bean.tags, CheetahString::from("tagA"));
606 assert_eq!(decoded_bean.keys, CheetahString::from("key1 key2"));
607 assert_eq!(decoded_bean.store_host, CheetahString::from("127.0.0.1:10911"));
608 assert_eq!(decoded_bean.body_length, 1024);
609 assert_eq!(decoded_bean.msg_type, Some(MessageType::NormalMsg));
610 assert_eq!(decoded_bean.offset_msg_id, CheetahString::from("offset-001"));
611 assert_eq!(decoded_bean.client_host, CheetahString::from("192.168.1.1:12345"));
612 }
613
614 #[test]
615 fn test_encode_and_decode_sub_before_context() {
616 let bean = TraceBean {
617 msg_id: CheetahString::from("msg-002"),
618 retry_times: 2,
619 keys: CheetahString::from("key3"),
620 ..Default::default()
621 };
622
623 let ctx = TraceContext {
624 trace_type: Some(TraceType::SubBefore),
625 time_stamp: 9876543210,
626 region_id: CheetahString::from("eu-west-1"),
627 group_name: CheetahString::from("consumer-group"),
628 request_id: CheetahString::from("req-123"),
629 trace_beans: Some(vec![bean]),
630 ..Default::default()
631 };
632
633 let transfer_bean = TraceDataEncoder::encoder_from_context_bean(&ctx).unwrap();
634 let decoded_contexts = TraceDataEncoder::decoder_from_trace_data_string(&transfer_bean.trans_data);
635
636 assert_eq!(decoded_contexts.len(), 1);
637 let decoded = &decoded_contexts[0];
638 assert_eq!(decoded.trace_type, Some(TraceType::SubBefore));
639 assert_eq!(decoded.time_stamp, 9876543210);
640 assert_eq!(decoded.region_id, CheetahString::from("eu-west-1"));
641 assert_eq!(decoded.group_name, CheetahString::from("consumer-group"));
642 assert_eq!(decoded.request_id, CheetahString::from("req-123"));
643 assert!(decoded.trace_beans.is_some());
644 let decoded_bean = &decoded.trace_beans.as_ref().unwrap()[0];
645 assert_eq!(decoded_bean.msg_id, CheetahString::from("msg-002"));
646 assert_eq!(decoded_bean.retry_times, 2);
647 assert_eq!(decoded_bean.keys, CheetahString::from("key3"));
648 }
649
650 #[test]
651 fn test_encode_and_decode_sub_after_context() {
652 let bean = TraceBean {
653 msg_id: CheetahString::from("msg-003"),
654 keys: CheetahString::from("key4 key5"),
655 ..Default::default()
656 };
657
658 let ctx = TraceContext {
659 trace_type: Some(TraceType::SubAfter),
660 request_id: CheetahString::from("req-456"),
661 cost_time: 200,
662 is_success: false,
663 context_code: 1,
664 time_stamp: 1122334455,
665 group_name: CheetahString::from("consumer-group-2"),
666 trace_beans: Some(vec![bean]),
667 ..Default::default()
668 };
669
670 let transfer_bean = TraceDataEncoder::encoder_from_context_bean(&ctx).unwrap();
671 let decoded_contexts = TraceDataEncoder::decoder_from_trace_data_string(&transfer_bean.trans_data);
672
673 assert_eq!(decoded_contexts.len(), 1);
674 let decoded = &decoded_contexts[0];
675 assert_eq!(decoded.trace_type, Some(TraceType::SubAfter));
676 assert_eq!(decoded.request_id, CheetahString::from("req-456"));
677 assert_eq!(decoded.cost_time, 200);
678 assert!(!decoded.is_success);
679 assert_eq!(decoded.context_code, 1);
680 assert_eq!(decoded.time_stamp, 1122334455);
681 assert_eq!(decoded.group_name, CheetahString::from("consumer-group-2"));
682 assert!(decoded.trace_beans.is_some());
683 let decoded_bean = &decoded.trace_beans.as_ref().unwrap()[0];
684 assert_eq!(decoded_bean.msg_id, CheetahString::from("msg-003"));
685 assert_eq!(decoded_bean.keys, CheetahString::from("key4 key5"));
686 }
687
688 #[test]
689 fn test_encode_and_decode_sub_after_cloud_channel() {
690 let bean = TraceBean {
691 msg_id: CheetahString::from("msg-004"),
692 keys: CheetahString::from("key6"),
693 ..Default::default()
694 };
695
696 let ctx = TraceContext {
697 trace_type: Some(TraceType::SubAfter),
698 request_id: CheetahString::from("req-789"),
699 cost_time: 150,
700 is_success: true,
701 context_code: 0,
702 access_channel: Some(AccessChannel::Cloud),
703 time_stamp: 1122334455,
704 group_name: CheetahString::from("consumer-group-cloud"),
705 trace_beans: Some(vec![bean]),
706 ..Default::default()
707 };
708
709 let transfer_bean = TraceDataEncoder::encoder_from_context_bean(&ctx).unwrap();
710 let decoded_contexts = TraceDataEncoder::decoder_from_trace_data_string(&transfer_bean.trans_data);
711
712 assert_eq!(decoded_contexts.len(), 1);
713 let decoded = &decoded_contexts[0];
714 assert_eq!(decoded.trace_type, Some(TraceType::SubAfter));
715 assert_eq!(decoded.request_id, CheetahString::from("req-789"));
716 assert_eq!(decoded.cost_time, 150);
717 assert!(decoded.is_success);
718 assert_eq!(decoded.context_code, 0);
719 assert_eq!(decoded.time_stamp, 0);
720 assert_eq!(decoded.group_name, CheetahString::default());
721 }
722
723 #[test]
724 fn test_encode_and_decode_end_transaction_context() {
725 let bean = TraceBean {
726 topic: CheetahString::from("tx-topic"),
727 msg_id: CheetahString::from("msg-tx-001"),
728 tags: CheetahString::from("tx-tag"),
729 keys: CheetahString::from("tx-key"),
730 store_host: CheetahString::from("127.0.0.1:10911"),
731 msg_type: Some(MessageType::TransMsgCommit),
732 transaction_id: Some(CheetahString::from("tx-id-001")),
733 transaction_state: Some(LocalTransactionState::CommitMessage),
734 from_transaction_check: true,
735 ..Default::default()
736 };
737
738 let ctx = TraceContext {
739 trace_type: Some(TraceType::EndTransaction),
740 time_stamp: 1234567890123,
741 region_id: CheetahString::from("ap-southeast-1"),
742 group_name: CheetahString::from("tx-group"),
743 trace_beans: Some(vec![bean]),
744 ..Default::default()
745 };
746
747 let transfer_bean = TraceDataEncoder::encoder_from_context_bean(&ctx).unwrap();
748 let decoded_contexts = TraceDataEncoder::decoder_from_trace_data_string(&transfer_bean.trans_data);
749
750 assert_eq!(decoded_contexts.len(), 1);
751 let decoded = &decoded_contexts[0];
752 assert_eq!(decoded.trace_type, Some(TraceType::EndTransaction));
753 assert_eq!(decoded.time_stamp, 1234567890123);
754 assert_eq!(decoded.region_id, CheetahString::from("ap-southeast-1"));
755 assert_eq!(decoded.group_name, CheetahString::from("tx-group"));
756 assert!(decoded.trace_beans.is_some());
757 let decoded_bean = &decoded.trace_beans.as_ref().unwrap()[0];
758 assert_eq!(decoded_bean.topic, CheetahString::from("tx-topic"));
759 assert_eq!(decoded_bean.msg_id, CheetahString::from("msg-tx-001"));
760 assert_eq!(decoded_bean.tags, CheetahString::from("tx-tag"));
761 assert_eq!(decoded_bean.keys, CheetahString::from("tx-key"));
762 assert_eq!(decoded_bean.store_host, CheetahString::from("127.0.0.1:10911"));
763 assert_eq!(decoded_bean.msg_type, Some(MessageType::TransMsgCommit));
764 assert_eq!(decoded_bean.transaction_id, Some(CheetahString::from("tx-id-001")));
765 assert_eq!(
766 decoded_bean.transaction_state,
767 Some(LocalTransactionState::CommitMessage)
768 );
769 assert!(decoded_bean.from_transaction_check);
770 }
771
772 #[test]
773 fn test_encode_and_decode_recall_context() {
774 let bean = TraceBean {
775 topic: CheetahString::from("recall-topic"),
776 msg_id: CheetahString::from("msg-recall-001"),
777 ..Default::default()
778 };
779
780 let ctx = TraceContext {
781 trace_type: Some(TraceType::Recall),
782 time_stamp: 9876543210987,
783 region_id: CheetahString::from("sa-east-1"),
784 group_name: CheetahString::from("recall-group"),
785 is_success: true,
786 trace_beans: Some(vec![bean]),
787 ..Default::default()
788 };
789
790 let transfer_bean = TraceDataEncoder::encoder_from_context_bean(&ctx).unwrap();
791 let decoded_contexts = TraceDataEncoder::decoder_from_trace_data_string(&transfer_bean.trans_data);
792
793 assert_eq!(decoded_contexts.len(), 1);
794 let decoded = &decoded_contexts[0];
795 assert_eq!(decoded.trace_type, Some(TraceType::Recall));
796 assert_eq!(decoded.time_stamp, 9876543210987);
797 assert_eq!(decoded.region_id, CheetahString::from("sa-east-1"));
798 assert_eq!(decoded.group_name, CheetahString::from("recall-group"));
799 assert!(decoded.is_success);
800 assert!(decoded.trace_beans.is_some());
801 let decoded_bean = &decoded.trace_beans.as_ref().unwrap()[0];
802 assert_eq!(decoded_bean.topic, CheetahString::from("recall-topic"));
803 assert_eq!(decoded_bean.msg_id, CheetahString::from("msg-recall-001"));
804 }
805
806 #[test]
807 fn test_encode_invalid_context() {
808 let ctx_no_beans = TraceContext {
809 trace_type: Some(TraceType::Pub),
810 trace_beans: None,
811 ..Default::default()
812 };
813 assert!(TraceDataEncoder::encoder_from_context_bean(&ctx_no_beans).is_none());
814
815 let ctx_empty_beans = TraceContext {
816 trace_type: Some(TraceType::Pub),
817 trace_beans: Some(vec![]),
818 ..Default::default()
819 };
820 assert!(TraceDataEncoder::encoder_from_context_bean(&ctx_empty_beans).is_none());
821 let ctx_no_trace_type = TraceContext {
822 trace_type: None,
823 trace_beans: Some(vec![TraceBean::default()]),
824 ..Default::default()
825 };
826 assert!(TraceDataEncoder::encoder_from_context_bean(&ctx_no_trace_type).is_none());
827 }
828
829 #[test]
830 fn test_transfer_bean_keys() {
831 let bean = TraceBean {
832 msg_id: CheetahString::from("msg-key-test"),
833 keys: CheetahString::from("key1 key2 key3"),
834 ..Default::default()
835 };
836
837 let ctx = TraceContext {
838 trace_type: Some(TraceType::Pub),
839 trace_beans: Some(vec![bean]),
840 ..Default::default()
841 };
842
843 let transfer_bean = TraceDataEncoder::encoder_from_context_bean(&ctx).unwrap();
844 assert!(transfer_bean.trans_key.contains(&CheetahString::from("msg-key-test")));
845 assert!(transfer_bean.trans_key.contains(&CheetahString::from("key1")));
846 assert!(transfer_bean.trans_key.contains(&CheetahString::from("key2")));
847 assert!(transfer_bean.trans_key.contains(&CheetahString::from("key3")));
848 }
849
850 #[test]
851 fn test_message_type_ordinal_conversion() {
852 assert_eq!(TraceDataEncoder::message_type_to_ordinal(MessageType::NormalMsg), 0);
853 assert_eq!(TraceDataEncoder::message_type_to_ordinal(MessageType::TransMsgHalf), 1);
854 assert_eq!(
855 TraceDataEncoder::message_type_to_ordinal(MessageType::TransMsgCommit),
856 2
857 );
858 assert_eq!(TraceDataEncoder::message_type_to_ordinal(MessageType::DelayMsg), 3);
859 assert_eq!(TraceDataEncoder::message_type_to_ordinal(MessageType::OrderMsg), 4);
860
861 assert_eq!(
862 TraceDataEncoder::message_type_from_ordinal(0),
863 Some(MessageType::NormalMsg)
864 );
865 assert_eq!(
866 TraceDataEncoder::message_type_from_ordinal(1),
867 Some(MessageType::TransMsgHalf)
868 );
869 assert_eq!(
870 TraceDataEncoder::message_type_from_ordinal(2),
871 Some(MessageType::TransMsgCommit)
872 );
873 assert_eq!(
874 TraceDataEncoder::message_type_from_ordinal(3),
875 Some(MessageType::DelayMsg)
876 );
877 assert_eq!(
878 TraceDataEncoder::message_type_from_ordinal(4),
879 Some(MessageType::OrderMsg)
880 );
881 assert_eq!(TraceDataEncoder::message_type_from_ordinal(99), None);
882 }
883
884 #[test]
885 fn test_parse_transaction_state() {
886 assert_eq!(
887 TraceDataEncoder::parse_transaction_state("COMMIT_MESSAGE"),
888 Some(LocalTransactionState::CommitMessage)
889 );
890 assert_eq!(
891 TraceDataEncoder::parse_transaction_state("ROLLBACK_MESSAGE"),
892 Some(LocalTransactionState::RollbackMessage)
893 );
894 assert_eq!(
895 TraceDataEncoder::parse_transaction_state("UNKNOW"),
896 Some(LocalTransactionState::Unknown)
897 );
898 assert_eq!(
899 TraceDataEncoder::parse_transaction_state("UNKNOWN"),
900 Some(LocalTransactionState::Unknown)
901 );
902 assert_eq!(TraceDataEncoder::parse_transaction_state("INVALID"), None);
903 }
904
905 fn raw_record(ctx: &TraceContext) -> String {
906 TraceDataEncoder::encoder_from_context_bean(ctx)
907 .unwrap()
908 .trans_data
909 .to_string()
910 }
911
912 fn corrupt_field(record: &str, field_index: usize, replacement: &str) -> String {
915 let has_trailing_field_splitor = record.ends_with(TraceConstants::FIELD_SPLITOR);
916 let body = if has_trailing_field_splitor {
917 &record[..record.len() - TraceConstants::FIELD_SPLITOR.len_utf8()]
918 } else {
919 record
920 };
921 let mut fields: Vec<&str> = body.split(TraceConstants::CONTENT_SPLITOR).collect();
922 fields[field_index] = replacement;
923 let mut joined = fields.join(&TraceConstants::CONTENT_SPLITOR.to_string());
924 if has_trailing_field_splitor {
925 joined.push(TraceConstants::FIELD_SPLITOR);
926 }
927 joined
928 }
929
930 #[test]
931 fn decode_malformed_truncated_record_between_two_valid_records_keeps_both_valid_ones_in_order() {
932 let pub_ctx = TraceContext {
933 trace_type: Some(TraceType::Pub),
934 time_stamp: 111,
935 region_id: CheetahString::from("r1"),
936 group_name: CheetahString::from("g1"),
937 cost_time: 10,
938 is_success: true,
939 trace_beans: Some(vec![TraceBean {
940 topic: CheetahString::from("topic-1"),
941 msg_id: CheetahString::from("msg-1"),
942 ..Default::default()
943 }]),
944 ..Default::default()
945 };
946 let sub_before_ctx = TraceContext {
947 trace_type: Some(TraceType::SubBefore),
948 time_stamp: 222,
949 region_id: CheetahString::from("r2"),
950 group_name: CheetahString::from("g2"),
951 request_id: CheetahString::from("req-2"),
952 trace_beans: Some(vec![TraceBean {
953 msg_id: CheetahString::from("msg-2"),
954 retry_times: 1,
955 ..Default::default()
956 }]),
957 ..Default::default()
958 };
959
960 let pub_raw = raw_record(&pub_ctx);
961 let sub_before_raw = raw_record(&sub_before_ctx);
962 let truncated = format!("Pub{sep}123{sep}region", sep = TraceConstants::CONTENT_SPLITOR);
964
965 let combined = format!(
966 "{pub_raw}{truncated}{sep}{sub_before_raw}",
967 sep = TraceConstants::FIELD_SPLITOR
968 );
969 let decoded = TraceDataEncoder::decoder_from_trace_data_string(&combined);
970
971 assert_eq!(decoded.len(), 2);
972 assert_eq!(decoded[0].trace_type, Some(TraceType::Pub));
973 assert_eq!(
974 decoded[0].trace_beans.as_ref().unwrap()[0].msg_id,
975 CheetahString::from("msg-1")
976 );
977 assert_eq!(decoded[1].trace_type, Some(TraceType::SubBefore));
978 assert_eq!(
979 decoded[1].trace_beans.as_ref().unwrap()[0].msg_id,
980 CheetahString::from("msg-2")
981 );
982 }
983
984 #[test]
985 fn decode_malformed_truncated_records_of_every_type_are_skipped() {
986 let sep = TraceConstants::CONTENT_SPLITOR;
987 let truncated_records = [
988 format!("Pub{sep}1{sep}region{sep}group{sep}topic"),
989 format!("SubBefore{sep}1{sep}region{sep}group"),
990 format!("SubAfter{sep}req{sep}msg"),
991 format!("EndTransaction{sep}1{sep}region{sep}group{sep}topic"),
992 format!("Recall{sep}1{sep}region"),
993 ];
994
995 for truncated in truncated_records {
996 let decoded = TraceDataEncoder::decoder_from_trace_data_string(&truncated);
997 assert!(
998 decoded.is_empty(),
999 "expected truncated record to be skipped: {truncated:?}"
1000 );
1001 }
1002 }
1003
1004 #[test]
1005 fn decode_malformed_corrupted_timestamp_is_skipped_but_following_valid_record_decodes() {
1006 let pub_ctx = TraceContext {
1007 trace_type: Some(TraceType::Pub),
1008 time_stamp: 111,
1009 region_id: CheetahString::from("r1"),
1010 group_name: CheetahString::from("g1"),
1011 cost_time: 10,
1012 is_success: true,
1013 trace_beans: Some(vec![TraceBean {
1014 topic: CheetahString::from("topic-1"),
1015 msg_id: CheetahString::from("msg-1"),
1016 ..Default::default()
1017 }]),
1018 ..Default::default()
1019 };
1020 let corrupted_pub = corrupt_field(&raw_record(&pub_ctx), 1, "not-a-number");
1021
1022 let recall_ctx = TraceContext {
1023 trace_type: Some(TraceType::Recall),
1024 time_stamp: 333,
1025 region_id: CheetahString::from("r3"),
1026 group_name: CheetahString::from("g3"),
1027 is_success: true,
1028 trace_beans: Some(vec![TraceBean {
1029 topic: CheetahString::from("topic-3"),
1030 msg_id: CheetahString::from("msg-3"),
1031 ..Default::default()
1032 }]),
1033 ..Default::default()
1034 };
1035 let recall_raw = raw_record(&recall_ctx);
1036
1037 let combined = format!("{corrupted_pub}{recall_raw}");
1038 let decoded = TraceDataEncoder::decoder_from_trace_data_string(&combined);
1039
1040 assert_eq!(decoded.len(), 1);
1041 assert_eq!(decoded[0].trace_type, Some(TraceType::Recall));
1042 assert_eq!(
1043 decoded[0].trace_beans.as_ref().unwrap()[0].msg_id,
1044 CheetahString::from("msg-3")
1045 );
1046 }
1047
1048 #[test]
1049 fn decode_malformed_corrupted_cost_time_is_skipped_but_following_valid_record_decodes() {
1050 let pub_ctx = TraceContext {
1051 trace_type: Some(TraceType::Pub),
1052 time_stamp: 111,
1053 region_id: CheetahString::from("r1"),
1054 group_name: CheetahString::from("g1"),
1055 cost_time: 10,
1056 is_success: true,
1057 trace_beans: Some(vec![TraceBean {
1058 topic: CheetahString::from("topic-1"),
1059 msg_id: CheetahString::from("msg-1"),
1060 ..Default::default()
1061 }]),
1062 ..Default::default()
1063 };
1064 let corrupted_pub = corrupt_field(&raw_record(&pub_ctx), 10, "not-a-number");
1066
1067 let sub_before_ctx = TraceContext {
1068 trace_type: Some(TraceType::SubBefore),
1069 time_stamp: 222,
1070 region_id: CheetahString::from("r2"),
1071 group_name: CheetahString::from("g2"),
1072 request_id: CheetahString::from("req-2"),
1073 trace_beans: Some(vec![TraceBean {
1074 msg_id: CheetahString::from("msg-2"),
1075 retry_times: 1,
1076 ..Default::default()
1077 }]),
1078 ..Default::default()
1079 };
1080 let sub_before_raw = raw_record(&sub_before_ctx);
1081
1082 let combined = format!("{corrupted_pub}{sub_before_raw}");
1083 let decoded = TraceDataEncoder::decoder_from_trace_data_string(&combined);
1084
1085 assert_eq!(decoded.len(), 1);
1086 assert_eq!(decoded[0].trace_type, Some(TraceType::SubBefore));
1087 }
1088
1089 #[test]
1090 fn decode_malformed_corrupted_retry_times_is_skipped_but_following_valid_record_decodes() {
1091 let sub_before_ctx = TraceContext {
1092 trace_type: Some(TraceType::SubBefore),
1093 time_stamp: 222,
1094 region_id: CheetahString::from("r2"),
1095 group_name: CheetahString::from("g2"),
1096 request_id: CheetahString::from("req-2"),
1097 trace_beans: Some(vec![TraceBean {
1098 msg_id: CheetahString::from("msg-2"),
1099 retry_times: 1,
1100 ..Default::default()
1101 }]),
1102 ..Default::default()
1103 };
1104 let corrupted_sub_before = corrupt_field(&raw_record(&sub_before_ctx), 6, "not-a-number");
1106
1107 let pub_ctx = TraceContext {
1108 trace_type: Some(TraceType::Pub),
1109 time_stamp: 111,
1110 region_id: CheetahString::from("r1"),
1111 group_name: CheetahString::from("g1"),
1112 cost_time: 10,
1113 is_success: true,
1114 trace_beans: Some(vec![TraceBean {
1115 topic: CheetahString::from("topic-1"),
1116 msg_id: CheetahString::from("msg-1"),
1117 ..Default::default()
1118 }]),
1119 ..Default::default()
1120 };
1121 let pub_raw = raw_record(&pub_ctx);
1122
1123 let combined = format!("{corrupted_sub_before}{pub_raw}");
1124 let decoded = TraceDataEncoder::decoder_from_trace_data_string(&combined);
1125
1126 assert_eq!(decoded.len(), 1);
1127 assert_eq!(decoded[0].trace_type, Some(TraceType::Pub));
1128 }
1129
1130 #[test]
1131 fn decode_malformed_unknown_type_and_repeated_separators_create_no_contexts() {
1132 let unknown = format!(
1133 "Bogus{content_sep}foo{content_sep}bar",
1134 content_sep = TraceConstants::CONTENT_SPLITOR
1135 );
1136 let combined = format!(
1137 "{unknown}{field_sep}{field_sep}{field_sep}",
1138 field_sep = TraceConstants::FIELD_SPLITOR
1139 );
1140
1141 let decoded = TraceDataEncoder::decoder_from_trace_data_string(&combined);
1142
1143 assert!(decoded.is_empty());
1144 }
1145}