1#[cfg(all(not(feature = "std"), feature = "alloc"))]
28use alloc::string::{String, ToString};
29use core::{fmt, str::FromStr};
30#[cfg(feature = "serde")]
31use serde::{Deserialize, Deserializer, Serialize, Serializer};
32use thiserror::Error;
33
34#[derive(Debug, Clone, PartialEq, Eq, Error)]
40pub enum TraceContextError {
41 #[error("invalid traceparent format")]
43 InvalidFormat,
44 #[error("unsupported traceparent version: must be \"00\"")]
46 UnsupportedVersion,
47 #[error("trace-id must not be all zeros")]
49 ZeroTraceId,
50 #[error("span-id must not be all zeros")]
52 ZeroSpanId,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
64#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
65#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
66pub struct TraceId([u8; 16]);
67
68impl TraceId {
69 #[must_use]
78 pub fn new() -> Self {
79 Self(*uuid::Uuid::new_v4().as_bytes())
80 }
81
82 #[must_use]
86 pub fn from_bytes(bytes: [u8; 16]) -> Option<Self> {
87 if bytes == [0u8; 16] {
88 None
89 } else {
90 Some(Self(bytes))
91 }
92 }
93
94 #[must_use]
96 pub fn as_bytes(&self) -> &[u8; 16] {
97 &self.0
98 }
99
100 #[must_use]
103 pub fn is_zero(&self) -> bool {
104 self.0 == [0u8; 16]
105 }
106
107 #[must_use]
109 pub fn to_hex(&self) -> String {
110 self.to_string()
111 }
112}
113
114impl Default for TraceId {
115 fn default() -> Self {
116 Self::new()
117 }
118}
119
120impl fmt::Display for TraceId {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 for b in &self.0 {
123 write!(f, "{b:02x}")?;
124 }
125 Ok(())
126 }
127}
128
129impl FromStr for TraceId {
130 type Err = TraceContextError;
131
132 fn from_str(s: &str) -> Result<Self, Self::Err> {
133 if s.len() != 32 {
134 return Err(TraceContextError::InvalidFormat);
135 }
136 let mut bytes = [0u8; 16];
137 for (i, b) in bytes.iter_mut().enumerate() {
138 *b = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16)
139 .map_err(|_| TraceContextError::InvalidFormat)?;
140 }
141 if bytes == [0u8; 16] {
142 return Err(TraceContextError::ZeroTraceId);
143 }
144 Ok(Self(bytes))
145 }
146}
147
148#[cfg(feature = "serde")]
149impl Serialize for TraceId {
150 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
151 serializer.serialize_str(&self.to_string())
152 }
153}
154
155#[cfg(feature = "serde")]
156impl<'de> Deserialize<'de> for TraceId {
157 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
158 let s = String::deserialize(deserializer)?;
159 s.parse().map_err(serde::de::Error::custom)
160 }
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
172#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
173#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
174pub struct SpanId([u8; 8]);
175
176impl SpanId {
177 #[must_use]
186 pub fn new() -> Self {
187 let uuid = uuid::Uuid::new_v4();
189 let b = uuid.as_bytes();
190 let mut arr = [0u8; 8];
191 arr.copy_from_slice(&b[..8]);
192 if arr == [0u8; 8] {
194 arr[0] = 1;
195 }
196 Self(arr)
197 }
198
199 #[must_use]
203 pub fn from_bytes(bytes: [u8; 8]) -> Option<Self> {
204 if bytes == [0u8; 8] {
205 None
206 } else {
207 Some(Self(bytes))
208 }
209 }
210
211 #[must_use]
213 pub fn as_bytes(&self) -> &[u8; 8] {
214 &self.0
215 }
216
217 #[must_use]
219 pub fn is_zero(&self) -> bool {
220 self.0 == [0u8; 8]
221 }
222
223 #[must_use]
225 pub fn to_hex(&self) -> String {
226 self.to_string()
227 }
228}
229
230impl Default for SpanId {
231 fn default() -> Self {
232 Self::new()
233 }
234}
235
236impl fmt::Display for SpanId {
237 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238 for b in &self.0 {
239 write!(f, "{b:02x}")?;
240 }
241 Ok(())
242 }
243}
244
245impl FromStr for SpanId {
246 type Err = TraceContextError;
247
248 fn from_str(s: &str) -> Result<Self, Self::Err> {
249 if s.len() != 16 {
250 return Err(TraceContextError::InvalidFormat);
251 }
252 let mut bytes = [0u8; 8];
253 for (i, b) in bytes.iter_mut().enumerate() {
254 *b = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16)
255 .map_err(|_| TraceContextError::InvalidFormat)?;
256 }
257 if bytes == [0u8; 8] {
258 return Err(TraceContextError::ZeroSpanId);
259 }
260 Ok(Self(bytes))
261 }
262}
263
264#[cfg(feature = "serde")]
265impl Serialize for SpanId {
266 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
267 serializer.serialize_str(&self.to_string())
268 }
269}
270
271#[cfg(feature = "serde")]
272impl<'de> Deserialize<'de> for SpanId {
273 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
274 let s = String::deserialize(deserializer)?;
275 s.parse().map_err(serde::de::Error::custom)
276 }
277}
278
279#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
287#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
288#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
289#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
290pub struct SamplingFlags(u8);
291
292impl SamplingFlags {
293 pub const SAMPLED: u8 = 0x01;
295
296 #[must_use]
298 pub const fn from_byte(b: u8) -> Self {
299 Self(b)
300 }
301
302 #[must_use]
304 pub const fn sampled() -> Self {
305 Self(Self::SAMPLED)
306 }
307
308 #[must_use]
310 pub const fn not_sampled() -> Self {
311 Self(0x00)
312 }
313
314 #[must_use]
316 pub const fn is_sampled(&self) -> bool {
317 self.0 & Self::SAMPLED != 0
318 }
319
320 #[must_use]
322 pub const fn as_byte(&self) -> u8 {
323 self.0
324 }
325}
326
327impl fmt::Display for SamplingFlags {
328 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329 write!(f, "{:02x}", self.0)
330 }
331}
332
333#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
360#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
361#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
362pub struct TraceContext {
363 pub trace_id: TraceId,
365 pub span_id: SpanId,
367 pub flags: SamplingFlags,
369}
370
371impl TraceContext {
372 #[must_use]
382 pub fn new() -> Self {
383 Self {
384 trace_id: TraceId::new(),
385 span_id: SpanId::new(),
386 flags: SamplingFlags::sampled(),
387 }
388 }
389
390 #[must_use]
401 pub fn child_span(&self) -> Self {
402 Self {
403 trace_id: self.trace_id,
404 span_id: SpanId::new(),
405 flags: self.flags,
406 }
407 }
408
409 #[must_use]
413 pub fn header_value(&self) -> String {
414 self.to_string()
415 }
416
417 #[must_use]
426 pub fn header_name(&self) -> &'static str {
427 "traceparent"
428 }
429}
430
431#[cfg(feature = "std")]
436impl crate::header_id::HeaderId for TraceContext {
437 const HEADER_NAME: &'static str = "traceparent";
438
439 fn as_str(&self) -> std::borrow::Cow<'_, str> {
440 std::borrow::Cow::Owned(self.to_string())
441 }
442}
443
444#[cfg(all(not(feature = "std"), feature = "alloc"))]
445impl crate::header_id::HeaderId for TraceContext {
446 const HEADER_NAME: &'static str = "traceparent";
447
448 fn as_str(&self) -> alloc::borrow::Cow<'_, str> {
449 alloc::borrow::Cow::Owned(self.to_string())
450 }
451}
452
453impl Default for TraceContext {
456 fn default() -> Self {
457 Self::new()
458 }
459}
460
461impl fmt::Display for TraceContext {
462 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
463 write!(f, "00-{}-{}-{}", self.trace_id, self.span_id, self.flags)
464 }
465}
466
467impl FromStr for TraceContext {
468 type Err = TraceContextError;
469
470 fn from_str(s: &str) -> Result<Self, Self::Err> {
476 let parts: [&str; 4] = {
477 let mut iter = s.splitn(5, '-');
478 let version = iter.next().ok_or(TraceContextError::InvalidFormat)?;
479 let trace_id = iter.next().ok_or(TraceContextError::InvalidFormat)?;
480 let span_id = iter.next().ok_or(TraceContextError::InvalidFormat)?;
481 let flags = iter.next().ok_or(TraceContextError::InvalidFormat)?;
482 if version == "00" && iter.next().is_some() {
484 return Err(TraceContextError::InvalidFormat);
485 }
486 [version, trace_id, span_id, flags]
487 };
488
489 if parts[0] != "00" {
490 return Err(TraceContextError::UnsupportedVersion);
491 }
492
493 let trace_id: TraceId = parts[1].parse()?;
494 let span_id: SpanId = parts[2].parse()?;
495
496 if parts[3].len() != 2 {
497 return Err(TraceContextError::InvalidFormat);
498 }
499 let flags_byte =
500 u8::from_str_radix(parts[3], 16).map_err(|_| TraceContextError::InvalidFormat)?;
501 let flags = SamplingFlags::from_byte(flags_byte);
502
503 Ok(Self {
504 trace_id,
505 span_id,
506 flags,
507 })
508 }
509}
510
511#[cfg(feature = "serde")]
512impl Serialize for TraceContext {
513 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
514 serializer.serialize_str(&self.to_string())
515 }
516}
517
518#[cfg(feature = "serde")]
519impl<'de> Deserialize<'de> for TraceContext {
520 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
521 let s = String::deserialize(deserializer)?;
522 s.parse().map_err(serde::de::Error::custom)
523 }
524}
525
526#[cfg(test)]
531mod tests {
532 use super::*;
533
534 const SAMPLE: &str = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01";
535
536 #[test]
539 fn trace_id_new_not_zero() {
540 assert!(!TraceId::new().is_zero());
541 }
542
543 #[test]
544 fn trace_id_display_is_32_hex() {
545 let id = TraceId::new();
546 let s = id.to_string();
547 assert_eq!(s.len(), 32);
548 assert!(s.chars().all(|c| c.is_ascii_hexdigit()));
549 }
550
551 #[test]
552 fn trace_id_parse_roundtrip() {
553 let id = TraceId::new();
554 let back: TraceId = id.to_string().parse().unwrap();
555 assert_eq!(id, back);
556 }
557
558 #[test]
559 fn trace_id_parse_rejects_all_zeros() {
560 let err = "00000000000000000000000000000000"
561 .parse::<TraceId>()
562 .unwrap_err();
563 assert_eq!(err, TraceContextError::ZeroTraceId);
564 }
565
566 #[test]
567 fn trace_id_parse_rejects_wrong_length() {
568 assert!("abc".parse::<TraceId>().is_err());
569 }
570
571 #[test]
572 fn trace_id_from_bytes_rejects_zeros() {
573 assert!(TraceId::from_bytes([0u8; 16]).is_none());
574 }
575
576 #[test]
579 fn span_id_new_not_zero() {
580 assert!(!SpanId::new().is_zero());
581 }
582
583 #[test]
584 fn span_id_display_is_16_hex() {
585 let id = SpanId::new();
586 let s = id.to_string();
587 assert_eq!(s.len(), 16);
588 assert!(s.chars().all(|c| c.is_ascii_hexdigit()));
589 }
590
591 #[test]
592 fn span_id_parse_roundtrip() {
593 let id = SpanId::new();
594 let back: SpanId = id.to_string().parse().unwrap();
595 assert_eq!(id, back);
596 }
597
598 #[test]
599 fn span_id_parse_rejects_all_zeros() {
600 let err = "0000000000000000".parse::<SpanId>().unwrap_err();
601 assert_eq!(err, TraceContextError::ZeroSpanId);
602 }
603
604 #[test]
607 fn sampling_flags_sampled() {
608 let f = SamplingFlags::sampled();
609 assert!(f.is_sampled());
610 assert_eq!(f.to_string(), "01");
611 }
612
613 #[test]
614 fn sampling_flags_not_sampled() {
615 let f = SamplingFlags::not_sampled();
616 assert!(!f.is_sampled());
617 assert_eq!(f.to_string(), "00");
618 }
619
620 #[test]
621 fn sampling_flags_from_byte() {
622 assert!(SamplingFlags::from_byte(0x01).is_sampled());
623 assert!(SamplingFlags::from_byte(0x03).is_sampled()); assert!(!SamplingFlags::from_byte(0x02).is_sampled());
625 }
626
627 #[test]
630 fn parse_sample_traceparent() {
631 let tc: TraceContext = SAMPLE.parse().unwrap();
632 assert!(tc.flags.is_sampled());
633 assert_eq!(tc.to_string(), SAMPLE);
634 }
635
636 #[test]
637 fn trace_context_roundtrip() {
638 let tc = TraceContext::new();
639 let back: TraceContext = tc.to_string().parse().unwrap();
640 assert_eq!(tc, back);
641 }
642
643 #[test]
644 fn trace_context_child_span_same_trace() {
645 let parent = TraceContext::new();
646 let child = parent.child_span();
647 assert_eq!(child.trace_id, parent.trace_id);
648 assert_ne!(child.span_id, parent.span_id);
649 assert_eq!(child.flags, parent.flags);
650 }
651
652 #[test]
653 fn parse_not_sampled() {
654 let s = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00";
655 let tc: TraceContext = s.parse().unwrap();
656 assert!(!tc.flags.is_sampled());
657 }
658
659 #[test]
660 fn parse_rejects_unsupported_version() {
661 let err = "01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
662 .parse::<TraceContext>()
663 .unwrap_err();
664 assert_eq!(err, TraceContextError::UnsupportedVersion);
665 }
666
667 #[test]
668 fn parse_rejects_too_few_fields() {
669 assert!(
670 "00-4bf92f3577b34da6a3ce929d0e0e4736"
671 .parse::<TraceContext>()
672 .is_err()
673 );
674 }
675
676 #[test]
677 fn parse_rejects_extra_fields_for_version_00() {
678 let s = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01-extra";
679 assert!(s.parse::<TraceContext>().is_err());
680 }
681
682 #[test]
683 fn parse_rejects_zero_trace_id() {
684 let s = "00-00000000000000000000000000000000-00f067aa0ba902b7-01";
685 assert!(s.parse::<TraceContext>().is_err());
686 }
687
688 #[test]
689 fn parse_rejects_zero_span_id() {
690 let s = "00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000000-01";
691 assert!(s.parse::<TraceContext>().is_err());
692 }
693
694 #[cfg(feature = "serde")]
695 #[test]
696 fn trace_context_serde_roundtrip() {
697 let tc: TraceContext = SAMPLE.parse().unwrap();
698 let json = serde_json::to_string(&tc).unwrap();
699 let back: TraceContext = serde_json::from_str(&json).unwrap();
700 assert_eq!(tc, back);
701 }
702
703 #[cfg(feature = "serde")]
704 #[test]
705 fn trace_id_serde_roundtrip() {
706 let id = TraceId::new();
707 let json = serde_json::to_string(&id).unwrap();
708 let back: TraceId = serde_json::from_str(&json).unwrap();
709 assert_eq!(id, back);
710 }
711
712 #[test]
715 fn trace_id_from_bytes_valid() {
716 let bytes = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
717 let id = TraceId::from_bytes(bytes).unwrap();
718 assert_eq!(id.as_bytes(), &bytes);
719 assert!(!id.is_zero());
720 }
721
722 #[test]
723 fn trace_id_as_bytes_roundtrip() {
724 let id = TraceId::new();
725 let bytes = *id.as_bytes();
726 let back = TraceId::from_bytes(bytes).unwrap();
727 assert_eq!(id, back);
728 }
729
730 #[test]
731 fn trace_id_to_hex() {
732 let id = TraceId::new();
733 assert_eq!(id.to_hex(), id.to_string());
734 assert_eq!(id.to_hex().len(), 32);
735 }
736
737 #[test]
738 fn trace_id_default_not_zero() {
739 let id = TraceId::default();
740 assert!(!id.is_zero());
741 }
742
743 #[test]
744 fn trace_id_parse_rejects_invalid_hex() {
745 let err = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
746 .parse::<TraceId>()
747 .unwrap_err();
748 assert_eq!(err, TraceContextError::InvalidFormat);
749 }
750
751 #[test]
754 fn span_id_from_bytes_valid() {
755 let bytes = [1u8, 2, 3, 4, 5, 6, 7, 8];
756 let id = SpanId::from_bytes(bytes).unwrap();
757 assert_eq!(id.as_bytes(), &bytes);
758 assert!(!id.is_zero());
759 }
760
761 #[test]
762 fn span_id_from_bytes_rejects_zeros() {
763 assert!(SpanId::from_bytes([0u8; 8]).is_none());
764 }
765
766 #[test]
767 fn span_id_as_bytes_roundtrip() {
768 let id = SpanId::new();
769 let bytes = *id.as_bytes();
770 let back = SpanId::from_bytes(bytes).unwrap();
771 assert_eq!(id, back);
772 }
773
774 #[test]
775 fn span_id_to_hex() {
776 let id = SpanId::new();
777 assert_eq!(id.to_hex(), id.to_string());
778 assert_eq!(id.to_hex().len(), 16);
779 }
780
781 #[test]
782 fn span_id_default_not_zero() {
783 let id = SpanId::default();
784 assert!(!id.is_zero());
785 }
786
787 #[test]
788 fn span_id_parse_rejects_wrong_length() {
789 assert!("abc".parse::<SpanId>().is_err());
790 }
791
792 #[test]
793 fn span_id_parse_rejects_invalid_hex() {
794 let err = "zzzzzzzzzzzzzzzz".parse::<SpanId>().unwrap_err();
795 assert_eq!(err, TraceContextError::InvalidFormat);
796 }
797
798 #[test]
801 fn sampling_flags_default_is_not_sampled() {
802 let f = SamplingFlags::default();
803 assert!(!f.is_sampled());
804 assert_eq!(f.as_byte(), 0x00);
805 }
806
807 #[test]
808 fn sampling_flags_as_byte() {
809 assert_eq!(SamplingFlags::sampled().as_byte(), 0x01);
810 assert_eq!(SamplingFlags::not_sampled().as_byte(), 0x00);
811 assert_eq!(SamplingFlags::from_byte(0xAB).as_byte(), 0xAB);
812 }
813
814 #[test]
817 fn trace_context_default_is_sampled() {
818 let tc = TraceContext::default();
819 assert!(tc.flags.is_sampled());
820 assert!(!tc.trace_id.is_zero());
821 assert!(!tc.span_id.is_zero());
822 }
823
824 #[test]
825 fn trace_context_header_value() {
826 let tc: TraceContext = SAMPLE.parse().unwrap();
827 assert_eq!(tc.header_value(), SAMPLE);
828 assert_eq!(tc.header_value(), tc.to_string());
829 }
830
831 #[test]
834 fn trace_context_error_display() {
835 assert_eq!(
836 TraceContextError::InvalidFormat.to_string(),
837 "invalid traceparent format"
838 );
839 assert_eq!(
840 TraceContextError::UnsupportedVersion.to_string(),
841 "unsupported traceparent version: must be \"00\""
842 );
843 assert_eq!(
844 TraceContextError::ZeroTraceId.to_string(),
845 "trace-id must not be all zeros"
846 );
847 assert_eq!(
848 TraceContextError::ZeroSpanId.to_string(),
849 "span-id must not be all zeros"
850 );
851 }
852
853 #[test]
856 fn parse_rejects_invalid_flags_hex() {
857 let s = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-zz";
858 assert!(s.parse::<TraceContext>().is_err());
859 }
860
861 #[test]
862 fn parse_rejects_flags_wrong_length() {
863 let s = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-1";
864 assert!(s.parse::<TraceContext>().is_err());
865 }
866
867 #[cfg(feature = "serde")]
870 #[test]
871 fn span_id_serde_roundtrip() {
872 let id = SpanId::new();
873 let json = serde_json::to_string(&id).unwrap();
874 let back: SpanId = serde_json::from_str(&json).unwrap();
875 assert_eq!(id, back);
876 }
877
878 #[cfg(feature = "serde")]
879 #[test]
880 fn trace_id_serde_deserialize_error() {
881 let result: Result<TraceId, _> = serde_json::from_str("\"not-valid\"");
882 assert!(result.is_err());
883 }
884
885 #[cfg(feature = "serde")]
886 #[test]
887 fn span_id_serde_deserialize_error() {
888 let result: Result<SpanId, _> = serde_json::from_str("\"not-valid\"");
889 assert!(result.is_err());
890 }
891
892 #[cfg(feature = "serde")]
893 #[test]
894 fn trace_context_serde_deserialize_error() {
895 let result: Result<TraceContext, _> = serde_json::from_str("\"not-valid\"");
896 assert!(result.is_err());
897 }
898}