use crate::util::DetRng;
use core::fmt;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TraceId {
high: u64,
low: u64,
}
impl TraceId {
#[must_use]
pub const fn new(high: u64, low: u64) -> Self {
Self { high, low }
}
#[must_use]
pub const fn from_u128(value: u128) -> Self {
Self {
high: (value >> 64) as u64,
low: value as u64,
}
}
#[must_use]
pub const fn as_u128(self) -> u128 {
((self.high as u128) << 64) | (self.low as u128)
}
#[must_use]
pub const fn high(self) -> u64 {
self.high
}
#[must_use]
pub const fn low(self) -> u64 {
self.low
}
#[must_use]
pub fn new_random(rng: &mut DetRng) -> Self {
Self {
high: rng.next_u64(),
low: rng.next_u64(),
}
}
#[doc(hidden)]
#[must_use]
pub const fn new_for_test(value: u64) -> Self {
Self {
high: 0,
low: value,
}
}
pub const NIL: Self = Self { high: 0, low: 0 };
#[must_use]
pub const fn is_nil(self) -> bool {
self.high == 0 && self.low == 0
}
#[must_use]
pub fn to_w3c_string(self) -> String {
format!("{:016x}{:016x}", self.high, self.low)
}
#[must_use]
pub fn from_w3c_string(s: &str) -> Option<Self> {
if s.len() != 32 {
return None;
}
let high = u64::from_str_radix(&s[..16], 16).ok()?;
let low = u64::from_str_radix(&s[16..], 16).ok()?;
Some(Self { high, low })
}
}
impl fmt::Debug for TraceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "TraceId({:016x}{:016x})", self.high, self.low)
}
}
impl fmt::Display for TraceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:016x}", self.high)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct SymbolSpanId(u64);
impl SymbolSpanId {
#[must_use]
pub const fn new(id: u64) -> Self {
Self(id)
}
#[must_use]
pub const fn as_u64(self) -> u64 {
self.0
}
#[must_use]
pub fn new_random(rng: &mut DetRng) -> Self {
Self(rng.next_u64())
}
#[doc(hidden)]
#[must_use]
pub const fn new_for_test(value: u64) -> Self {
Self(value)
}
pub const NIL: Self = Self(0);
}
impl fmt::Debug for SymbolSpanId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SymbolSpanId({:016x})", self.0)
}
}
impl fmt::Display for SymbolSpanId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:08x}", (self.0 & 0xFFFF_FFFF) as u32)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn trace_id_w3c_roundtrip() {
let id = TraceId::new(0x1234_5678_9abc_def0, 0xfedc_ba98_7654_3210);
let w3c = id.to_w3c_string();
let parsed = TraceId::from_w3c_string(&w3c).expect("parse should succeed");
assert_eq!(id, parsed);
}
#[test]
fn trace_id_nil_detection() {
let id = TraceId::NIL;
assert!(id.is_nil());
let id = TraceId::new(1, 0);
assert!(!id.is_nil());
}
#[test]
fn span_id_display_is_stable() {
let id = SymbolSpanId::new(0x1234_5678_9abc_def0);
assert_eq!(format!("{id}"), "9abcdef0");
}
#[test]
fn trace_id_u128_roundtrip() {
let values: [u128; 4] = [0, 1, u128::MAX, 0x0001_0002_0003_0004_0005_0006_0007_0008];
for v in values {
let id = TraceId::from_u128(v);
assert_eq!(id.as_u128(), v, "u128 roundtrip failed for {v:#x}");
}
}
#[test]
fn trace_id_high_low_consistent_with_u128() {
let high = 0xAABB_CCDD_EEFF_0011u64;
let low = 0x2233_4455_6677_8899u64;
let id = TraceId::new(high, low);
assert_eq!(id.high(), high);
assert_eq!(id.low(), low);
let expected_u128 = (u128::from(high) << 64) | u128::from(low);
assert_eq!(id.as_u128(), expected_u128);
assert_eq!(TraceId::from_u128(expected_u128), id);
}
#[test]
fn trace_id_deterministic_generation_with_fixed_seed() {
let mut rng_a = DetRng::new(999);
let mut rng_b = DetRng::new(999);
let id_a = TraceId::new_random(&mut rng_a);
let id_b = TraceId::new_random(&mut rng_b);
assert_eq!(id_a, id_b, "same seed must produce same TraceId");
}
#[test]
fn trace_id_different_seeds_produce_different_ids() {
let mut rng_a = DetRng::new(1);
let mut rng_b = DetRng::new(2);
let id_a = TraceId::new_random(&mut rng_a);
let id_b = TraceId::new_random(&mut rng_b);
assert_ne!(id_a, id_b);
}
#[test]
fn trace_id_w3c_invalid_length_returns_none() {
assert!(TraceId::from_w3c_string("").is_none());
assert!(TraceId::from_w3c_string("0123456789abcdef").is_none()); assert!(TraceId::from_w3c_string("0123456789abcdef0123456789abcdef0").is_none());
}
#[test]
fn trace_id_w3c_invalid_hex_returns_none() {
assert!(TraceId::from_w3c_string("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz").is_none());
}
#[test]
fn trace_id_nil_is_zero() {
let nil = TraceId::NIL;
assert_eq!(nil.high(), 0);
assert_eq!(nil.low(), 0);
assert_eq!(nil.as_u128(), 0);
assert!(nil.is_nil());
}
#[test]
fn trace_id_new_for_test_has_zero_high() {
let id = TraceId::new_for_test(42);
assert_eq!(id.high(), 0);
assert_eq!(id.low(), 42);
assert!(!id.is_nil());
}
#[test]
fn symbol_span_id_roundtrip() {
let values: [u64; 4] = [0, 1, u64::MAX, 0xDEAD_BEEF_CAFE_BABE];
for v in values {
let id = SymbolSpanId::new(v);
assert_eq!(id.as_u64(), v);
}
}
#[test]
fn symbol_span_id_deterministic_generation() {
let mut rng_a = DetRng::new(777);
let mut rng_b = DetRng::new(777);
let id_a = SymbolSpanId::new_random(&mut rng_a);
let id_b = SymbolSpanId::new_random(&mut rng_b);
assert_eq!(id_a, id_b);
}
#[test]
fn symbol_span_id_nil_is_zero() {
assert_eq!(SymbolSpanId::NIL.as_u64(), 0);
}
#[test]
fn trace_id_w3c_max_values() {
let id = TraceId::new(u64::MAX, u64::MAX);
let w3c = id.to_w3c_string();
assert_eq!(w3c, "ffffffffffffffffffffffffffffffff");
let parsed = TraceId::from_w3c_string(&w3c).unwrap();
assert_eq!(parsed, id);
}
#[test]
fn trace_id_clone_copy_eq_hash() {
use std::collections::HashSet;
let a = TraceId::new(1, 2);
let b = a; let c = a;
assert_eq!(a, b);
assert_eq!(a, c);
assert_ne!(a, TraceId::new(3, 4));
let mut set = HashSet::new();
set.insert(a);
assert!(set.contains(&b));
}
#[test]
fn symbol_span_id_clone_copy_eq_hash() {
use std::collections::HashSet;
let a = SymbolSpanId::new(42);
let b = a; let c = a;
assert_eq!(a, b);
assert_eq!(a, c);
assert_ne!(a, SymbolSpanId::new(99));
let mut set = HashSet::new();
set.insert(a);
assert!(set.contains(&b));
}
}