use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::Metadata;
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct TraceContext {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trace_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub span_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_span_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trace_state: Option<String>,
#[serde(default, skip_serializing_if = "Metadata::is_empty")]
pub metadata: Metadata,
}
impl TraceContext {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn from_trace_id(trace_id: impl Into<String>) -> Self {
Self {
trace_id: Some(trace_id.into()),
..Self::default()
}
}
#[must_use]
pub fn from_trace_parent(trace_parent: impl Into<String>) -> Self {
let trace_parent = trace_parent.into();
let parts = trace_parent.split('-').collect::<Vec<_>>();
if parts.len() == 4 && valid_trace_parent_parts(&parts) {
let mut metadata = Metadata::default();
metadata.insert(
"trace_flags".to_string(),
Value::String(parts[3].to_string()),
);
Self {
trace_id: Some(parts[1].to_string()),
parent_span_id: Some(parts[2].to_string()),
metadata,
..Self::default()
}
} else {
Self::from_trace_id(trace_parent)
}
}
#[must_use]
pub fn with_span_id(mut self, span_id: impl Into<String>) -> Self {
self.span_id = Some(span_id.into());
self
}
#[must_use]
pub fn with_parent_span_id(mut self, parent_span_id: impl Into<String>) -> Self {
self.parent_span_id = Some(parent_span_id.into());
self
}
#[must_use]
pub fn with_trace_state(mut self, trace_state: impl Into<String>) -> Self {
self.trace_state = Some(trace_state.into());
self
}
#[must_use]
pub fn with_metadata(mut self, metadata: Metadata) -> Self {
self.metadata = metadata;
self
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.trace_id.is_none()
&& self.span_id.is_none()
&& self.parent_span_id.is_none()
&& self.trace_state.is_none()
&& self.metadata.is_empty()
}
}
fn valid_trace_parent_parts(parts: &[&str]) -> bool {
let [version, trace_id, parent_span_id, trace_flags] = parts else {
return false;
};
*version != "ff"
&& is_lower_hex(version, 2)
&& is_lower_hex(trace_id, 32)
&& trace_id.bytes().any(|byte| byte != b'0')
&& is_lower_hex(parent_span_id, 16)
&& parent_span_id.bytes().any(|byte| byte != b'0')
&& is_lower_hex(trace_flags, 2)
}
fn is_lower_hex(value: &str, len: usize) -> bool {
value.len() == len
&& value
.bytes()
.all(|byte| byte.is_ascii_digit() || matches!(byte, b'a'..=b'f'))
}