use std::any::Any;
use std::collections::BTreeMap;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Metadata {
trace_id: Option<String>,
span_id: Option<String>,
baggage: BTreeMap<String, String>,
}
impl Metadata {
pub fn new() -> Self {
Self::default()
}
pub fn with_trace(trace_id: impl Into<String>, span_id: impl Into<String>) -> Self {
Self { trace_id: Some(trace_id.into()), span_id: Some(span_id.into()), baggage: BTreeMap::new() }
}
pub fn trace_id(&self) -> Option<&str> {
self.trace_id.as_deref()
}
pub fn span_id(&self) -> Option<&str> {
self.span_id.as_deref()
}
pub fn set_trace_id(&mut self, trace_id: impl Into<String>) {
self.trace_id = Some(trace_id.into());
}
pub fn set_span_id(&mut self, span_id: impl Into<String>) {
self.span_id = Some(span_id.into());
}
pub fn set_baggage(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.baggage.insert(key.into(), value.into());
}
pub fn baggage(&self, key: &str) -> Option<&str> {
self.baggage.get(key).map(String::as_str)
}
pub fn baggage_iter(&self) -> impl Iterator<Item = (&str, &str)> {
self.baggage.iter().map(|(k, v)| (k.as_str(), v.as_str()))
}
pub fn is_empty(&self) -> bool {
self.trace_id.is_none() && self.span_id.is_none() && self.baggage.is_empty()
}
}
#[must_use = "the span guard must be held for the duration of message handling"]
pub struct SpanGuard(#[allow(dead_code)] Option<Box<dyn Any + Send>>);
impl SpanGuard {
pub fn none() -> Self {
SpanGuard(None)
}
pub fn holding(guard: impl Any + Send) -> Self {
SpanGuard(Some(Box::new(guard)))
}
}
impl Default for SpanGuard {
fn default() -> Self {
SpanGuard::none()
}
}
pub trait MessageInterceptor: Send + Sync {
fn before_handle(&self, meta: &Metadata) -> SpanGuard {
let _ = meta;
SpanGuard::none()
}
fn outgoing(&self, parent: &Metadata) -> Metadata {
parent.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_by_default() {
let m = Metadata::new();
assert!(m.is_empty());
assert_eq!(m.trace_id(), None);
}
#[test]
fn carries_trace_and_baggage() {
let mut m = Metadata::with_trace("trace-1", "span-1");
m.set_baggage("tenant", "acme");
assert_eq!(m.trace_id(), Some("trace-1"));
assert_eq!(m.span_id(), Some("span-1"));
assert_eq!(m.baggage("tenant"), Some("acme"));
assert!(!m.is_empty());
}
struct Noop;
impl MessageInterceptor for Noop {}
#[test]
fn default_interceptor_propagates() {
let i = Noop;
let mut m = Metadata::with_trace("t", "s");
m.set_baggage("k", "v");
let child = i.outgoing(&m);
assert_eq!(child, m);
let _guard = i.before_handle(&m);
}
}