#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::string::{String, ToString};
use core::{fmt, str::FromStr};
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum TraceContextError {
#[error("invalid traceparent format")]
InvalidFormat,
#[error("unsupported traceparent version: must be \"00\"")]
UnsupportedVersion,
#[error("trace-id must not be all zeros")]
ZeroTraceId,
#[error("span-id must not be all zeros")]
ZeroSpanId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct TraceId([u8; 16]);
impl TraceId {
#[must_use]
pub fn new() -> Self {
Self(*uuid::Uuid::new_v4().as_bytes())
}
#[must_use]
pub fn from_bytes(bytes: [u8; 16]) -> Option<Self> {
if bytes == [0u8; 16] {
None
} else {
Some(Self(bytes))
}
}
#[must_use]
pub fn as_bytes(&self) -> &[u8; 16] {
&self.0
}
#[must_use]
pub fn is_zero(&self) -> bool {
self.0 == [0u8; 16]
}
#[must_use]
pub fn to_hex(&self) -> String {
self.to_string()
}
}
impl Default for TraceId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for TraceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for b in &self.0 {
write!(f, "{b:02x}")?;
}
Ok(())
}
}
impl FromStr for TraceId {
type Err = TraceContextError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != 32 {
return Err(TraceContextError::InvalidFormat);
}
let mut bytes = [0u8; 16];
for (i, b) in bytes.iter_mut().enumerate() {
*b = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16)
.map_err(|_| TraceContextError::InvalidFormat)?;
}
if bytes == [0u8; 16] {
return Err(TraceContextError::ZeroTraceId);
}
Ok(Self(bytes))
}
}
#[cfg(feature = "serde")]
impl Serialize for TraceId {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for TraceId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct SpanId([u8; 8]);
impl SpanId {
#[must_use]
pub fn new() -> Self {
let uuid = uuid::Uuid::new_v4();
let b = uuid.as_bytes();
let mut arr = [0u8; 8];
arr.copy_from_slice(&b[..8]);
if arr == [0u8; 8] {
arr[0] = 1;
}
Self(arr)
}
#[must_use]
pub fn from_bytes(bytes: [u8; 8]) -> Option<Self> {
if bytes == [0u8; 8] {
None
} else {
Some(Self(bytes))
}
}
#[must_use]
pub fn as_bytes(&self) -> &[u8; 8] {
&self.0
}
#[must_use]
pub fn is_zero(&self) -> bool {
self.0 == [0u8; 8]
}
#[must_use]
pub fn to_hex(&self) -> String {
self.to_string()
}
}
impl Default for SpanId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for SpanId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for b in &self.0 {
write!(f, "{b:02x}")?;
}
Ok(())
}
}
impl FromStr for SpanId {
type Err = TraceContextError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != 16 {
return Err(TraceContextError::InvalidFormat);
}
let mut bytes = [0u8; 8];
for (i, b) in bytes.iter_mut().enumerate() {
*b = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16)
.map_err(|_| TraceContextError::InvalidFormat)?;
}
if bytes == [0u8; 8] {
return Err(TraceContextError::ZeroSpanId);
}
Ok(Self(bytes))
}
}
#[cfg(feature = "serde")]
impl Serialize for SpanId {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for SpanId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct SamplingFlags(u8);
impl SamplingFlags {
pub const SAMPLED: u8 = 0x01;
#[must_use]
pub const fn from_byte(b: u8) -> Self {
Self(b)
}
#[must_use]
pub const fn sampled() -> Self {
Self(Self::SAMPLED)
}
#[must_use]
pub const fn not_sampled() -> Self {
Self(0x00)
}
#[must_use]
pub const fn is_sampled(&self) -> bool {
self.0 & Self::SAMPLED != 0
}
#[must_use]
pub const fn as_byte(&self) -> u8 {
self.0
}
}
impl fmt::Display for SamplingFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:02x}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct TraceContext {
pub trace_id: TraceId,
pub span_id: SpanId,
pub flags: SamplingFlags,
}
impl TraceContext {
#[must_use]
pub fn new() -> Self {
Self {
trace_id: TraceId::new(),
span_id: SpanId::new(),
flags: SamplingFlags::sampled(),
}
}
#[must_use]
pub fn child_span(&self) -> Self {
Self {
trace_id: self.trace_id,
span_id: SpanId::new(),
flags: self.flags,
}
}
#[must_use]
pub fn header_value(&self) -> String {
self.to_string()
}
#[must_use]
pub fn header_name(&self) -> &'static str {
"traceparent"
}
}
#[cfg(feature = "std")]
impl crate::header_id::HeaderId for TraceContext {
const HEADER_NAME: &'static str = "traceparent";
fn as_str(&self) -> std::borrow::Cow<'_, str> {
std::borrow::Cow::Owned(self.to_string())
}
}
#[cfg(all(not(feature = "std"), feature = "alloc"))]
impl crate::header_id::HeaderId for TraceContext {
const HEADER_NAME: &'static str = "traceparent";
fn as_str(&self) -> alloc::borrow::Cow<'_, str> {
alloc::borrow::Cow::Owned(self.to_string())
}
}
impl Default for TraceContext {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for TraceContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "00-{}-{}-{}", self.trace_id, self.span_id, self.flags)
}
}
impl FromStr for TraceContext {
type Err = TraceContextError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: [&str; 4] = {
let mut iter = s.splitn(5, '-');
let version = iter.next().ok_or(TraceContextError::InvalidFormat)?;
let trace_id = iter.next().ok_or(TraceContextError::InvalidFormat)?;
let span_id = iter.next().ok_or(TraceContextError::InvalidFormat)?;
let flags = iter.next().ok_or(TraceContextError::InvalidFormat)?;
if version == "00" && iter.next().is_some() {
return Err(TraceContextError::InvalidFormat);
}
[version, trace_id, span_id, flags]
};
if parts[0] != "00" {
return Err(TraceContextError::UnsupportedVersion);
}
let trace_id: TraceId = parts[1].parse()?;
let span_id: SpanId = parts[2].parse()?;
if parts[3].len() != 2 {
return Err(TraceContextError::InvalidFormat);
}
let flags_byte =
u8::from_str_radix(parts[3], 16).map_err(|_| TraceContextError::InvalidFormat)?;
let flags = SamplingFlags::from_byte(flags_byte);
Ok(Self {
trace_id,
span_id,
flags,
})
}
}
#[cfg(feature = "serde")]
impl Serialize for TraceContext {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for TraceContext {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE: &str = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01";
#[test]
fn trace_id_new_not_zero() {
assert!(!TraceId::new().is_zero());
}
#[test]
fn trace_id_display_is_32_hex() {
let id = TraceId::new();
let s = id.to_string();
assert_eq!(s.len(), 32);
assert!(s.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn trace_id_parse_roundtrip() {
let id = TraceId::new();
let back: TraceId = id.to_string().parse().unwrap();
assert_eq!(id, back);
}
#[test]
fn trace_id_parse_rejects_all_zeros() {
let err = "00000000000000000000000000000000"
.parse::<TraceId>()
.unwrap_err();
assert_eq!(err, TraceContextError::ZeroTraceId);
}
#[test]
fn trace_id_parse_rejects_wrong_length() {
assert!("abc".parse::<TraceId>().is_err());
}
#[test]
fn trace_id_from_bytes_rejects_zeros() {
assert!(TraceId::from_bytes([0u8; 16]).is_none());
}
#[test]
fn span_id_new_not_zero() {
assert!(!SpanId::new().is_zero());
}
#[test]
fn span_id_display_is_16_hex() {
let id = SpanId::new();
let s = id.to_string();
assert_eq!(s.len(), 16);
assert!(s.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn span_id_parse_roundtrip() {
let id = SpanId::new();
let back: SpanId = id.to_string().parse().unwrap();
assert_eq!(id, back);
}
#[test]
fn span_id_parse_rejects_all_zeros() {
let err = "0000000000000000".parse::<SpanId>().unwrap_err();
assert_eq!(err, TraceContextError::ZeroSpanId);
}
#[test]
fn sampling_flags_sampled() {
let f = SamplingFlags::sampled();
assert!(f.is_sampled());
assert_eq!(f.to_string(), "01");
}
#[test]
fn sampling_flags_not_sampled() {
let f = SamplingFlags::not_sampled();
assert!(!f.is_sampled());
assert_eq!(f.to_string(), "00");
}
#[test]
fn sampling_flags_from_byte() {
assert!(SamplingFlags::from_byte(0x01).is_sampled());
assert!(SamplingFlags::from_byte(0x03).is_sampled()); assert!(!SamplingFlags::from_byte(0x02).is_sampled());
}
#[test]
fn parse_sample_traceparent() {
let tc: TraceContext = SAMPLE.parse().unwrap();
assert!(tc.flags.is_sampled());
assert_eq!(tc.to_string(), SAMPLE);
}
#[test]
fn trace_context_roundtrip() {
let tc = TraceContext::new();
let back: TraceContext = tc.to_string().parse().unwrap();
assert_eq!(tc, back);
}
#[test]
fn trace_context_child_span_same_trace() {
let parent = TraceContext::new();
let child = parent.child_span();
assert_eq!(child.trace_id, parent.trace_id);
assert_ne!(child.span_id, parent.span_id);
assert_eq!(child.flags, parent.flags);
}
#[test]
fn parse_not_sampled() {
let s = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00";
let tc: TraceContext = s.parse().unwrap();
assert!(!tc.flags.is_sampled());
}
#[test]
fn parse_rejects_unsupported_version() {
let err = "01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
.parse::<TraceContext>()
.unwrap_err();
assert_eq!(err, TraceContextError::UnsupportedVersion);
}
#[test]
fn parse_rejects_too_few_fields() {
assert!(
"00-4bf92f3577b34da6a3ce929d0e0e4736"
.parse::<TraceContext>()
.is_err()
);
}
#[test]
fn parse_rejects_extra_fields_for_version_00() {
let s = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01-extra";
assert!(s.parse::<TraceContext>().is_err());
}
#[test]
fn parse_rejects_zero_trace_id() {
let s = "00-00000000000000000000000000000000-00f067aa0ba902b7-01";
assert!(s.parse::<TraceContext>().is_err());
}
#[test]
fn parse_rejects_zero_span_id() {
let s = "00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000000-01";
assert!(s.parse::<TraceContext>().is_err());
}
#[cfg(feature = "serde")]
#[test]
fn trace_context_serde_roundtrip() {
let tc: TraceContext = SAMPLE.parse().unwrap();
let json = serde_json::to_string(&tc).unwrap();
let back: TraceContext = serde_json::from_str(&json).unwrap();
assert_eq!(tc, back);
}
#[cfg(feature = "serde")]
#[test]
fn trace_id_serde_roundtrip() {
let id = TraceId::new();
let json = serde_json::to_string(&id).unwrap();
let back: TraceId = serde_json::from_str(&json).unwrap();
assert_eq!(id, back);
}
#[test]
fn trace_id_from_bytes_valid() {
let bytes = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let id = TraceId::from_bytes(bytes).unwrap();
assert_eq!(id.as_bytes(), &bytes);
assert!(!id.is_zero());
}
#[test]
fn trace_id_as_bytes_roundtrip() {
let id = TraceId::new();
let bytes = *id.as_bytes();
let back = TraceId::from_bytes(bytes).unwrap();
assert_eq!(id, back);
}
#[test]
fn trace_id_to_hex() {
let id = TraceId::new();
assert_eq!(id.to_hex(), id.to_string());
assert_eq!(id.to_hex().len(), 32);
}
#[test]
fn trace_id_default_not_zero() {
let id = TraceId::default();
assert!(!id.is_zero());
}
#[test]
fn trace_id_parse_rejects_invalid_hex() {
let err = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
.parse::<TraceId>()
.unwrap_err();
assert_eq!(err, TraceContextError::InvalidFormat);
}
#[test]
fn span_id_from_bytes_valid() {
let bytes = [1u8, 2, 3, 4, 5, 6, 7, 8];
let id = SpanId::from_bytes(bytes).unwrap();
assert_eq!(id.as_bytes(), &bytes);
assert!(!id.is_zero());
}
#[test]
fn span_id_from_bytes_rejects_zeros() {
assert!(SpanId::from_bytes([0u8; 8]).is_none());
}
#[test]
fn span_id_as_bytes_roundtrip() {
let id = SpanId::new();
let bytes = *id.as_bytes();
let back = SpanId::from_bytes(bytes).unwrap();
assert_eq!(id, back);
}
#[test]
fn span_id_to_hex() {
let id = SpanId::new();
assert_eq!(id.to_hex(), id.to_string());
assert_eq!(id.to_hex().len(), 16);
}
#[test]
fn span_id_default_not_zero() {
let id = SpanId::default();
assert!(!id.is_zero());
}
#[test]
fn span_id_parse_rejects_wrong_length() {
assert!("abc".parse::<SpanId>().is_err());
}
#[test]
fn span_id_parse_rejects_invalid_hex() {
let err = "zzzzzzzzzzzzzzzz".parse::<SpanId>().unwrap_err();
assert_eq!(err, TraceContextError::InvalidFormat);
}
#[test]
fn sampling_flags_default_is_not_sampled() {
let f = SamplingFlags::default();
assert!(!f.is_sampled());
assert_eq!(f.as_byte(), 0x00);
}
#[test]
fn sampling_flags_as_byte() {
assert_eq!(SamplingFlags::sampled().as_byte(), 0x01);
assert_eq!(SamplingFlags::not_sampled().as_byte(), 0x00);
assert_eq!(SamplingFlags::from_byte(0xAB).as_byte(), 0xAB);
}
#[test]
fn trace_context_default_is_sampled() {
let tc = TraceContext::default();
assert!(tc.flags.is_sampled());
assert!(!tc.trace_id.is_zero());
assert!(!tc.span_id.is_zero());
}
#[test]
fn trace_context_header_value() {
let tc: TraceContext = SAMPLE.parse().unwrap();
assert_eq!(tc.header_value(), SAMPLE);
assert_eq!(tc.header_value(), tc.to_string());
}
#[test]
fn trace_context_error_display() {
assert_eq!(
TraceContextError::InvalidFormat.to_string(),
"invalid traceparent format"
);
assert_eq!(
TraceContextError::UnsupportedVersion.to_string(),
"unsupported traceparent version: must be \"00\""
);
assert_eq!(
TraceContextError::ZeroTraceId.to_string(),
"trace-id must not be all zeros"
);
assert_eq!(
TraceContextError::ZeroSpanId.to_string(),
"span-id must not be all zeros"
);
}
#[test]
fn parse_rejects_invalid_flags_hex() {
let s = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-zz";
assert!(s.parse::<TraceContext>().is_err());
}
#[test]
fn parse_rejects_flags_wrong_length() {
let s = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-1";
assert!(s.parse::<TraceContext>().is_err());
}
#[cfg(feature = "serde")]
#[test]
fn span_id_serde_roundtrip() {
let id = SpanId::new();
let json = serde_json::to_string(&id).unwrap();
let back: SpanId = serde_json::from_str(&json).unwrap();
assert_eq!(id, back);
}
#[cfg(feature = "serde")]
#[test]
fn trace_id_serde_deserialize_error() {
let result: Result<TraceId, _> = serde_json::from_str("\"not-valid\"");
assert!(result.is_err());
}
#[cfg(feature = "serde")]
#[test]
fn span_id_serde_deserialize_error() {
let result: Result<SpanId, _> = serde_json::from_str("\"not-valid\"");
assert!(result.is_err());
}
#[cfg(feature = "serde")]
#[test]
fn trace_context_serde_deserialize_error() {
let result: Result<TraceContext, _> = serde_json::from_str("\"not-valid\"");
assert!(result.is_err());
}
}