1use std::{fmt, sync::Arc};
2
3pub const MAX_TRACE_CORRELATION_ID_BYTES: usize = 256;
4
5macro_rules! string_id {
6 ($name:ident) => {
7 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
8 pub struct $name(Arc<str>);
9
10 impl $name {
11 pub fn new(value: impl Into<Arc<str>>) -> Self {
12 Self(value.into())
13 }
14
15 pub fn as_str(&self) -> &str {
16 &self.0
17 }
18 }
19
20 impl From<&str> for $name {
21 fn from(value: &str) -> Self {
22 Self::new(value)
23 }
24 }
25
26 impl From<String> for $name {
27 fn from(value: String) -> Self {
28 Self::new(value)
29 }
30 }
31
32 impl fmt::Display for $name {
33 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
34 formatter.write_str(&self.0)
35 }
36 }
37 };
38}
39
40string_id!(ApplicationId);
41string_id!(ModuleId);
42string_id!(ServiceId);
43string_id!(OperationId);
44
45#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
46pub struct TraceId(u128);
47
48impl TraceId {
49 pub const fn from_u128(value: u128) -> Self {
50 Self(value)
51 }
52
53 pub const fn as_u128(self) -> u128 {
54 self.0
55 }
56}
57
58impl fmt::Display for TraceId {
59 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
60 write!(formatter, "{:032x}", self.0)
61 }
62}
63
64#[derive(Clone, Debug, Eq, Hash, PartialEq)]
65pub struct TraceCorrelationId(Arc<str>);
66
67#[derive(Clone, Copy, Debug, Eq, PartialEq)]
68pub enum TraceCorrelationIdError {
69 Empty,
70 TooLong,
71 ControlCharacter,
72}
73
74impl TraceCorrelationId {
75 pub fn new(value: impl Into<Arc<str>>) -> Result<Self, TraceCorrelationIdError> {
76 let value = value.into();
77 if value.is_empty() {
78 return Err(TraceCorrelationIdError::Empty);
79 }
80 if value.len() > MAX_TRACE_CORRELATION_ID_BYTES {
81 return Err(TraceCorrelationIdError::TooLong);
82 }
83 if value.chars().any(char::is_control) {
84 return Err(TraceCorrelationIdError::ControlCharacter);
85 }
86 Ok(Self(value))
87 }
88
89 pub fn as_str(&self) -> &str {
90 &self.0
91 }
92}
93
94impl fmt::Display for TraceCorrelationId {
95 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
96 formatter.write_str(&self.0)
97 }
98}
99
100#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
101pub struct SpanId(u64);
102
103impl SpanId {
104 pub const fn from_u64(value: u64) -> Self {
105 Self(value)
106 }
107
108 pub const fn as_u64(self) -> u64 {
109 self.0
110 }
111}
112
113impl fmt::Display for SpanId {
114 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
115 write!(formatter, "{:016x}", self.0)
116 }
117}
118
119#[derive(Clone, Debug, Eq, PartialEq)]
124pub struct CallContext {
125 application: ApplicationId,
126 module: ModuleId,
127 service: ServiceId,
128 operation: OperationId,
129 trace_id: TraceId,
130 trace_correlation_id: TraceCorrelationId,
131 span_id: SpanId,
132}
133
134impl CallContext {
135 pub fn new(
136 application: ApplicationId,
137 module: ModuleId,
138 service: ServiceId,
139 operation: OperationId,
140 trace_id: TraceId,
141 span_id: SpanId,
142 ) -> Self {
143 let trace_correlation_id = TraceCorrelationId(Arc::from(trace_id.to_string()));
144 Self {
145 application,
146 module,
147 service,
148 operation,
149 trace_id,
150 trace_correlation_id,
151 span_id,
152 }
153 }
154
155 pub fn with_trace_correlation_id(mut self, trace_correlation_id: TraceCorrelationId) -> Self {
156 self.trace_correlation_id = trace_correlation_id;
157 self
158 }
159
160 pub fn application(&self) -> &ApplicationId {
161 &self.application
162 }
163
164 pub fn module(&self) -> &ModuleId {
165 &self.module
166 }
167
168 pub fn service(&self) -> &ServiceId {
169 &self.service
170 }
171
172 pub fn operation(&self) -> &OperationId {
173 &self.operation
174 }
175
176 pub const fn trace_id(&self) -> TraceId {
177 self.trace_id
178 }
179
180 pub fn trace_correlation_id(&self) -> &TraceCorrelationId {
181 &self.trace_correlation_id
182 }
183
184 pub const fn span_id(&self) -> SpanId {
185 self.span_id
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn identifiers_have_stable_display_forms() {
195 assert_eq!(TraceId::from_u128(42).to_string().len(), 32);
196 assert_eq!(SpanId::from_u64(42).to_string().len(), 16);
197 assert_eq!(ServiceId::from("orders").to_string(), "orders");
198 }
199
200 #[test]
201 fn opaque_trace_correlation_id_is_bounded_and_rejects_controls() {
202 assert_eq!(
203 TraceCorrelationId::new("trace-1").unwrap().as_str(),
204 "trace-1"
205 );
206 assert_eq!(
207 TraceCorrelationId::new(""),
208 Err(TraceCorrelationIdError::Empty)
209 );
210 assert_eq!(
211 TraceCorrelationId::new("x".repeat(MAX_TRACE_CORRELATION_ID_BYTES + 1)),
212 Err(TraceCorrelationIdError::TooLong)
213 );
214 assert_eq!(
215 TraceCorrelationId::new("trace\n1"),
216 Err(TraceCorrelationIdError::ControlCharacter)
217 );
218 }
219}