1use std::fmt;
11use std::str::FromStr;
12
13use rand::Rng;
14use serde::{Deserialize, Serialize};
15
16#[derive(Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
20pub struct TraceId(pub [u8; 16]);
21
22impl TraceId {
23 pub const ZERO: Self = Self([0u8; 16]);
25
26 pub fn generate() -> Self {
28 let mut bytes = [0u8; 16];
29 rand::rng().fill_bytes(&mut bytes);
30 if bytes == [0u8; 16] {
32 bytes[15] = 1;
33 }
34 Self(bytes)
35 }
36
37 pub fn from_traceparent(s: &str) -> Option<(TraceId, SpanId, u8)> {
45 let parts: Vec<&str> = s.splitn(4, '-').collect();
46 if parts.len() != 4 {
47 return None;
48 }
49 if parts[0] != "00" {
51 return None;
52 }
53 let mut trace_bytes = [0u8; 16];
56 hex::decode_to_slice(parts[1], &mut trace_bytes).ok()?;
57 let mut span_bytes = [0u8; 8];
58 hex::decode_to_slice(parts[2], &mut span_bytes).ok()?;
59 let mut flags = [0u8; 1];
60 hex::decode_to_slice(parts[3], &mut flags).ok()?;
61
62 Some((TraceId(trace_bytes), SpanId(span_bytes), flags[0]))
63 }
64
65 pub fn to_traceparent_header(&self, span_id: SpanId, flags: u8) -> String {
69 format!("00-{self}-{span_id}-{flags:02x}")
70 }
71}
72
73impl fmt::Display for TraceId {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 f.write_str(&hex::encode(self.0))
76 }
77}
78
79impl fmt::Debug for TraceId {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 write!(f, "TraceId({self})")
82 }
83}
84
85#[derive(Debug, thiserror::Error)]
87#[error("invalid TraceId: expected 32 lowercase hex chars, got {0:?}")]
88pub struct TraceIdParseError(String);
89
90impl FromStr for TraceId {
91 type Err = TraceIdParseError;
92
93 fn from_str(s: &str) -> Result<Self, Self::Err> {
94 let mut bytes = [0u8; 16];
95 hex::decode_to_slice(s, &mut bytes).map_err(|_| TraceIdParseError(s.to_owned()))?;
96 Ok(TraceId(bytes))
97 }
98}
99
100#[derive(Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
104pub struct SpanId(pub [u8; 8]);
105
106impl SpanId {
107 pub const ZERO: Self = Self([0u8; 8]);
109
110 pub fn generate() -> Self {
112 let mut bytes = [0u8; 8];
113 rand::rng().fill_bytes(&mut bytes);
114 if bytes == [0u8; 8] {
115 bytes[7] = 1;
116 }
117 Self(bytes)
118 }
119}
120
121impl fmt::Display for SpanId {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 f.write_str(&hex::encode(self.0))
124 }
125}
126
127impl fmt::Debug for SpanId {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 write!(f, "SpanId({self})")
130 }
131}
132
133#[derive(Debug, thiserror::Error)]
135#[error("invalid SpanId: expected 16 lowercase hex chars, got {0:?}")]
136pub struct SpanIdParseError(String);
137
138impl FromStr for SpanId {
139 type Err = SpanIdParseError;
140
141 fn from_str(s: &str) -> Result<Self, Self::Err> {
142 let mut bytes = [0u8; 8];
143 hex::decode_to_slice(s, &mut bytes).map_err(|_| SpanIdParseError(s.to_owned()))?;
144 Ok(SpanId(bytes))
145 }
146}
147
148#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn generate_produces_nonzero_ids() {
156 let id = TraceId::generate();
157 assert_ne!(id, TraceId::ZERO);
158 }
159
160 #[test]
161 fn two_successive_generate_calls_differ() {
162 let a = TraceId::generate();
163 let b = TraceId::generate();
164 assert_ne!(a, b);
166 }
167
168 #[test]
169 fn display_produces_32_lowercase_hex_chars() {
170 let id = TraceId::generate();
171 let s = id.to_string();
172 assert_eq!(s.len(), 32);
173 assert!(
174 s.chars()
175 .all(|c| c.is_ascii_hexdigit() && !c.is_uppercase())
176 );
177 }
178
179 #[test]
180 fn from_str_roundtrips_through_display() {
181 let id = TraceId::generate();
182 let s = id.to_string();
183 let parsed: TraceId = s.parse().expect("parse must succeed");
184 assert_eq!(id, parsed);
185 }
186
187 #[test]
188 fn from_traceparent_extracts_correct_values() {
189 let s = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01";
190 let (tid, sid, flags) = TraceId::from_traceparent(s).expect("valid traceparent");
191 let expected_trace = hex::decode("4bf92f3577b34da6a3ce929d0e0e4736").unwrap();
193 assert_eq!(tid.0.as_slice(), expected_trace.as_slice());
194 let expected_span = hex::decode("00f067aa0ba902b7").unwrap();
196 assert_eq!(sid.0.as_slice(), expected_span.as_slice());
197 assert_eq!(flags, 0x01);
198 }
199
200 #[test]
201 fn from_traceparent_rejects_wrong_version() {
202 assert!(
203 TraceId::from_traceparent("01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01")
204 .is_none()
205 );
206 }
207
208 #[test]
209 fn from_traceparent_rejects_wrong_trace_length() {
210 assert!(
212 TraceId::from_traceparent("00-4bf92f3577b34da6a3ce929d0e0e47-00f067aa0ba902b7-01")
213 .is_none()
214 );
215 }
216
217 #[test]
218 fn from_traceparent_rejects_wrong_field_count() {
219 assert!(
220 TraceId::from_traceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7")
221 .is_none()
222 );
223 }
224
225 #[test]
226 fn from_traceparent_rejects_non_hex_chars() {
227 assert!(
228 TraceId::from_traceparent("00-zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz-00f067aa0ba902b7-01")
229 .is_none()
230 );
231 }
232
233 #[test]
234 fn to_traceparent_header_roundtrips_with_from_traceparent() {
235 let tid = TraceId::generate();
236 let sid = SpanId::generate();
237 let header = tid.to_traceparent_header(sid, 0x01);
238 let (parsed_tid, parsed_sid, flags) = TraceId::from_traceparent(&header).expect("valid");
239 assert_eq!(parsed_tid, tid);
240 assert_eq!(parsed_sid, sid);
241 assert_eq!(flags, 0x01);
242 }
243
244 #[test]
245 fn span_id_generate_nonzero() {
246 let sid = SpanId::generate();
247 assert_ne!(sid, SpanId::ZERO);
248 }
249
250 #[test]
251 fn span_id_display_16_lowercase_hex() {
252 let sid = SpanId::generate();
253 let s = sid.to_string();
254 assert_eq!(s.len(), 16);
255 assert!(
256 s.chars()
257 .all(|c| c.is_ascii_hexdigit() && !c.is_uppercase())
258 );
259 }
260}