use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[cfg_attr(feature = "server", derive(utoipa::ToSchema))]
pub struct TraceContext {
pub trace_id: String,
pub span_id: String,
pub trace_flags: u8,
pub trace_state: String,
}
impl TraceContext {
pub fn parse_traceparent(header: &str, trace_state: String) -> Result<Self, TraceError> {
let parts: Vec<&str> = header.split('-').collect();
let version = parts[0];
if !is_lower_hex(version, 2) || version == "ff" || parts.len() < 4 {
return Err(TraceError::BadHeader);
}
if version == "00" && parts.len() != 4 {
return Err(TraceError::BadHeader);
}
if !is_lower_hex(parts[1], 32) || is_all_zero(parts[1]) {
return Err(TraceError::BadHeader);
}
if !is_lower_hex(parts[2], 16) || is_all_zero(parts[2]) {
return Err(TraceError::BadHeader);
}
if !is_lower_hex(parts[3], 2) {
return Err(TraceError::BadHeader);
}
let trace_flags = u8::from_str_radix(parts[3], 16).map_err(|_| TraceError::BadHeader)?;
Ok(Self {
trace_id: parts[1].to_string(),
span_id: parts[2].to_string(),
trace_flags,
trace_state,
})
}
#[must_use]
pub fn to_traceparent(&self) -> String {
format!(
"00-{}-{}-{:02x}",
self.trace_id, self.span_id, self.trace_flags
)
}
}
#[derive(Debug, thiserror::Error)]
pub enum TraceError {
#[error("bad traceparent header")]
BadHeader,
}
fn is_lower_hex(value: &str, len: usize) -> bool {
value.len() == len
&& value
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
}
fn is_all_zero(value: &str) -> bool {
value.bytes().all(|byte| byte == b'0')
}