uptrakit_wire/
trace_context.rs1use std::fmt;
9
10use serde::{Deserialize, Serialize};
11
12use crate::limits::{
13 MAX_SPAN_ID_LEN, MAX_TRACE_ID_LEN, WireValidate, WireValidationError, check_opt_string_len,
14 check_string_len,
15};
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub struct TraceContext {
29 pub trace_id: String,
31 #[serde(skip_serializing_if = "Option::is_none", default)]
34 pub span_id: Option<String>,
35}
36
37impl TraceContext {
38 pub fn generate() -> Self {
43 Self {
44 trace_id: uuid::Uuid::new_v4().simple().to_string(),
45 span_id: None,
46 }
47 }
48}
49
50impl Default for TraceContext {
51 fn default() -> Self {
52 Self::generate()
53 }
54}
55
56impl fmt::Display for TraceContext {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 match &self.span_id {
59 Some(span) => write!(f, "{}:{}", self.trace_id, span),
60 None => f.write_str(&self.trace_id),
61 }
62 }
63}
64
65impl WireValidate for TraceContext {
66 fn wire_validate(&self) -> Result<(), WireValidationError> {
67 check_string_len(&self.trace_id, MAX_TRACE_ID_LEN, "trace_context.trace_id")?;
68 check_opt_string_len(&self.span_id, MAX_SPAN_ID_LEN, "trace_context.span_id")?;
69 Ok(())
70 }
71}
72
73pub fn current_trace_context() -> TraceContext {
79 TraceContext::generate()
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn generate_produces_valid_trace_id() {
88 let ctx = TraceContext::generate();
89 assert_eq!(ctx.trace_id.len(), 32, "trace_id must be 32 hex chars");
90 assert!(
91 ctx.trace_id.chars().all(|c| c.is_ascii_hexdigit()),
92 "trace_id must contain only hex characters"
93 );
94 assert!(ctx.span_id.is_none(), "generated context has no span_id");
95 }
96
97 #[test]
98 fn generate_produces_unique_ids() {
99 let a = TraceContext::generate();
100 let b = TraceContext::generate();
101 assert_ne!(a.trace_id, b.trace_id, "two generated IDs must differ");
102 }
103
104 #[test]
105 fn display_without_span_id() {
106 let ctx = TraceContext {
107 trace_id: "0123456789abcdef0123456789abcdef".to_string(),
108 span_id: None,
109 };
110 assert_eq!(ctx.to_string(), "0123456789abcdef0123456789abcdef");
111 }
112
113 #[test]
114 fn display_with_span_id() {
115 let ctx = TraceContext {
116 trace_id: "0123456789abcdef0123456789abcdef".to_string(),
117 span_id: Some("fedcba9876543210".to_string()),
118 };
119 assert_eq!(
120 ctx.to_string(),
121 "0123456789abcdef0123456789abcdef:fedcba9876543210"
122 );
123 }
124
125 #[test]
126 fn serde_roundtrip_with_span_id() {
127 let ctx = TraceContext {
128 trace_id: "0123456789abcdef0123456789abcdef".to_string(),
129 span_id: Some("fedcba9876543210".to_string()),
130 };
131 let json = serde_json::to_string(&ctx).unwrap();
132 assert!(json.contains("span_id"));
133 let deserialized: TraceContext = serde_json::from_str(&json).unwrap();
134 assert_eq!(deserialized, ctx);
135 }
136
137 #[test]
138 fn serde_roundtrip_without_span_id() {
139 let ctx = TraceContext {
140 trace_id: "0123456789abcdef0123456789abcdef".to_string(),
141 span_id: None,
142 };
143 let json = serde_json::to_string(&ctx).unwrap();
144 assert!(!json.contains("span_id"), "None span_id must be omitted");
145 let deserialized: TraceContext = serde_json::from_str(&json).unwrap();
146 assert_eq!(deserialized, ctx);
147 }
148
149 #[test]
150 fn deserialize_missing_span_id() {
151 let json = r#"{"trace_id":"0123456789abcdef0123456789abcdef"}"#;
152 let ctx: TraceContext = serde_json::from_str(json).unwrap();
153 assert_eq!(ctx.trace_id, "0123456789abcdef0123456789abcdef");
154 assert!(ctx.span_id.is_none());
155 }
156
157 #[test]
158 fn wire_validate_valid() {
159 let ctx = TraceContext {
160 trace_id: "0123456789abcdef0123456789abcdef".to_string(),
161 span_id: Some("fedcba9876543210".to_string()),
162 };
163 assert!(ctx.wire_validate().is_ok());
164 }
165
166 #[test]
167 fn wire_validate_trace_id_too_long() {
168 let ctx = TraceContext {
169 trace_id: "a".repeat(33),
170 span_id: None,
171 };
172 let err = ctx.wire_validate().unwrap_err();
173 assert_eq!(err.field, "trace_context.trace_id");
174 }
175
176 #[test]
177 fn wire_validate_span_id_too_long() {
178 let ctx = TraceContext {
179 trace_id: "0123456789abcdef0123456789abcdef".to_string(),
180 span_id: Some("a".repeat(17)),
181 };
182 let err = ctx.wire_validate().unwrap_err();
183 assert_eq!(err.field, "trace_context.span_id");
184 }
185
186 #[test]
187 fn current_trace_context_generates_valid() {
188 let ctx = current_trace_context();
189 assert!(ctx.wire_validate().is_ok());
190 }
191}