atomr_core/actor/
metadata.rs1use std::any::Any;
14use std::collections::BTreeMap;
15
16#[derive(Debug, Clone, Default, PartialEq, Eq)]
20pub struct Metadata {
21 trace_id: Option<String>,
22 span_id: Option<String>,
23 baggage: BTreeMap<String, String>,
24}
25
26impl Metadata {
27 pub fn new() -> Self {
29 Self::default()
30 }
31
32 pub fn with_trace(trace_id: impl Into<String>, span_id: impl Into<String>) -> Self {
34 Self { trace_id: Some(trace_id.into()), span_id: Some(span_id.into()), baggage: BTreeMap::new() }
35 }
36
37 pub fn trace_id(&self) -> Option<&str> {
39 self.trace_id.as_deref()
40 }
41
42 pub fn span_id(&self) -> Option<&str> {
44 self.span_id.as_deref()
45 }
46
47 pub fn set_trace_id(&mut self, trace_id: impl Into<String>) {
49 self.trace_id = Some(trace_id.into());
50 }
51
52 pub fn set_span_id(&mut self, span_id: impl Into<String>) {
54 self.span_id = Some(span_id.into());
55 }
56
57 pub fn set_baggage(&mut self, key: impl Into<String>, value: impl Into<String>) {
59 self.baggage.insert(key.into(), value.into());
60 }
61
62 pub fn baggage(&self, key: &str) -> Option<&str> {
64 self.baggage.get(key).map(String::as_str)
65 }
66
67 pub fn baggage_iter(&self) -> impl Iterator<Item = (&str, &str)> {
69 self.baggage.iter().map(|(k, v)| (k.as_str(), v.as_str()))
70 }
71
72 pub fn is_empty(&self) -> bool {
74 self.trace_id.is_none() && self.span_id.is_none() && self.baggage.is_empty()
75 }
76}
77
78#[must_use = "the span guard must be held for the duration of message handling"]
82pub struct SpanGuard(#[allow(dead_code)] Option<Box<dyn Any + Send>>);
83
84impl SpanGuard {
85 pub fn none() -> Self {
87 SpanGuard(None)
88 }
89
90 pub fn holding(guard: impl Any + Send) -> Self {
92 SpanGuard(Some(Box::new(guard)))
93 }
94}
95
96impl Default for SpanGuard {
97 fn default() -> Self {
98 SpanGuard::none()
99 }
100}
101
102pub trait MessageInterceptor: Send + Sync {
110 fn before_handle(&self, meta: &Metadata) -> SpanGuard {
114 let _ = meta;
115 SpanGuard::none()
116 }
117
118 fn outgoing(&self, parent: &Metadata) -> Metadata {
121 parent.clone()
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn empty_by_default() {
131 let m = Metadata::new();
132 assert!(m.is_empty());
133 assert_eq!(m.trace_id(), None);
134 }
135
136 #[test]
137 fn carries_trace_and_baggage() {
138 let mut m = Metadata::with_trace("trace-1", "span-1");
139 m.set_baggage("tenant", "acme");
140 assert_eq!(m.trace_id(), Some("trace-1"));
141 assert_eq!(m.span_id(), Some("span-1"));
142 assert_eq!(m.baggage("tenant"), Some("acme"));
143 assert!(!m.is_empty());
144 }
145
146 struct Noop;
147 impl MessageInterceptor for Noop {}
148
149 #[test]
150 fn default_interceptor_propagates() {
151 let i = Noop;
152 let mut m = Metadata::with_trace("t", "s");
153 m.set_baggage("k", "v");
154 let child = i.outgoing(&m);
155 assert_eq!(child, m);
156 let _guard = i.before_handle(&m);
157 }
158}