sc_observability_types/
tracing.rs1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{ActionName, ErrorCode, StateName, TargetCategory, ValueValidationError, error_codes};
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub struct TraceId(String);
10
11impl TraceId {
12 pub fn new(value: impl Into<String>) -> Result<Self, ValueValidationError> {
19 let value = value.into();
20 validate_lower_hex(
21 &value,
22 crate::constants::TRACE_ID_LEN,
23 &error_codes::TRACE_ID_INVALID,
24 )?;
25 Ok(Self(value))
26 }
27
28 #[must_use]
30 pub fn as_str(&self) -> &str {
31 &self.0
32 }
33}
34
35impl fmt::Display for TraceId {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 f.write_str(&self.0)
38 }
39}
40
41impl AsRef<str> for TraceId {
42 fn as_ref(&self) -> &str {
43 self.as_str()
44 }
45}
46
47impl TryFrom<String> for TraceId {
48 type Error = ValueValidationError;
49
50 fn try_from(value: String) -> Result<Self, Self::Error> {
51 Self::new(value)
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
57pub struct SpanId(String);
58
59impl SpanId {
60 pub fn new(value: impl Into<String>) -> Result<Self, ValueValidationError> {
67 let value = value.into();
68 validate_lower_hex(
69 &value,
70 crate::constants::SPAN_ID_LEN,
71 &error_codes::SPAN_ID_INVALID,
72 )?;
73 Ok(Self(value))
74 }
75
76 #[must_use]
78 pub fn as_str(&self) -> &str {
79 &self.0
80 }
81}
82
83impl fmt::Display for SpanId {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 f.write_str(&self.0)
86 }
87}
88
89impl AsRef<str> for SpanId {
90 fn as_ref(&self) -> &str {
91 self.as_str()
92 }
93}
94
95impl TryFrom<String> for SpanId {
96 type Error = ValueValidationError;
97
98 fn try_from(value: String) -> Result<Self, Self::Error> {
99 Self::new(value)
100 }
101}
102
103pub(crate) fn validate_lower_hex(
104 value: &str,
105 expected_len: usize,
106 code: &ErrorCode,
107) -> Result<(), ValueValidationError> {
108 if value.len() != expected_len {
109 return Err(ValueValidationError::with_code(
110 code.clone(),
111 format!("value must be {expected_len} lowercase hex characters"),
112 ));
113 }
114 if value
115 .chars()
116 .all(|ch| ch.is_ascii_hexdigit() && !ch.is_ascii_uppercase())
117 {
118 Ok(())
119 } else {
120 Err(ValueValidationError::with_code(
121 code.clone(),
122 "value must contain lowercase hex characters only",
123 ))
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
129pub struct TraceContext {
130 pub trace_id: TraceId,
132 pub span_id: SpanId,
134 pub parent_span_id: Option<SpanId>,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140pub struct StateTransition {
141 pub entity_kind: TargetCategory,
143 pub entity_id: Option<String>,
145 pub from_state: StateName,
147 pub to_state: StateName,
149 pub reason: Option<String>,
151 pub trigger: Option<ActionName>,
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn trace_and_span_ids_validate_w3c_shapes() {
161 assert!(TraceId::new("0123456789abcdef0123456789abcdef").is_ok());
162 assert_eq!(
163 TraceId::try_from("0123456789abcdef0123456789abcdef".to_string())
164 .expect("valid trace id")
165 .as_ref(),
166 "0123456789abcdef0123456789abcdef"
167 );
168 assert_eq!(
169 TraceId::new("0123456789abcdef0123456789abcdef")
170 .expect("valid trace id")
171 .to_string(),
172 "0123456789abcdef0123456789abcdef"
173 );
174 let short_trace = TraceId::new("0123456789abcdef0123456789abcde")
175 .expect_err("short trace id should fail");
176 assert_eq!(short_trace.code(), &error_codes::TRACE_ID_INVALID);
177 let uppercase_trace = TraceId::new("0123456789ABCDEF0123456789abcdef")
178 .expect_err("uppercase trace id should fail");
179 assert_eq!(uppercase_trace.code(), &error_codes::TRACE_ID_INVALID);
180
181 assert!(SpanId::new("0123456789abcdef").is_ok());
182 assert_eq!(
183 SpanId::try_from("0123456789abcdef".to_string())
184 .expect("valid span id")
185 .as_ref(),
186 "0123456789abcdef"
187 );
188 assert_eq!(
189 SpanId::new("0123456789abcdef")
190 .expect("valid span id")
191 .to_string(),
192 "0123456789abcdef"
193 );
194 let short_span = SpanId::new("0123456789abcde").expect_err("short span id should fail");
195 assert_eq!(short_span.code(), &error_codes::SPAN_ID_INVALID);
196 let uppercase_span =
197 SpanId::new("0123456789ABCDEf").expect_err("uppercase span id should fail");
198 assert_eq!(uppercase_span.code(), &error_codes::SPAN_ID_INVALID);
199 }
200}