use serde::{Deserialize, Serialize}; use std::fmt; use std::str::FromStr; use uuid::Uuid; pub const HEADER_X_CORRELATION_ID: &str = "x-correlation-id"; pub const HEADER_TRACEPARENT: &str = "traceparent"; pub const HEADER_TRACE_ID: &str = "trace-id"; pub const NATS_HEADER_CORRELATION_ID: &str = "correlation-id"; #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)] pub struct TenantId(String); impl TenantId { pub fn new(id: impl Into) -> Self { Self(id.into()) } pub fn is_empty(&self) -> bool { self.0.is_empty() } pub fn as_str(&self) -> &str { &self.0 } } impl fmt::Display for TenantId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl FromStr for TenantId { type Err = std::convert::Infallible; fn from_str(s: &str) -> Result { Ok(Self(s.to_string())) } } impl AsRef for TenantId { fn as_ref(&self) -> &str { &self.0 } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(transparent)] pub struct CorrelationId(String); impl CorrelationId { pub fn new(id: impl Into) -> Self { Self(id.into()) } pub fn generate() -> Self { Self(Uuid::new_v4().to_string()) } pub fn as_str(&self) -> &str { &self.0 } } impl fmt::Display for CorrelationId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl FromStr for CorrelationId { type Err = std::convert::Infallible; fn from_str(s: &str) -> Result { Ok(Self(s.to_string())) } } impl AsRef for CorrelationId { fn as_ref(&self) -> &str { &self.0 } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(transparent)] pub struct TraceId(String); impl TraceId { pub fn new(id: impl Into) -> Self { Self(id.into()) } pub fn as_str(&self) -> &str { &self.0 } pub fn is_valid_hex_32(&self) -> bool { is_valid_hex_32(self.as_str()) } } impl fmt::Display for TraceId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl FromStr for TraceId { type Err = std::convert::Infallible; fn from_str(s: &str) -> Result { Ok(Self(s.to_string())) } } impl AsRef for TraceId { fn as_ref(&self) -> &str { &self.0 } } pub fn trace_id_from_traceparent(traceparent: &str) -> Option<&str> { let mut parts = traceparent.split('-'); let version = parts.next()?; let trace_id = parts.next()?; let span_id = parts.next()?; let flags = parts.next()?; if version.len() != 2 || trace_id.len() != 32 || span_id.len() != 16 || flags.len() != 2 { return None; } if !trace_id.chars().all(|c| c.is_ascii_hexdigit()) || !span_id.chars().all(|c| c.is_ascii_hexdigit()) || !flags.chars().all(|c| c.is_ascii_hexdigit()) || !version.chars().all(|c| c.is_ascii_hexdigit()) { return None; } Some(trace_id) } pub fn traceparent_from_trace_id(trace_id: &TraceId) -> Option { if !trace_id.is_valid_hex_32() { return None; } let span_id = Uuid::new_v4().simple().to_string()[..16].to_string(); Some(format!("00-{}-{span_id}-01", trace_id.as_str())) } fn is_valid_hex_32(s: &str) -> bool { s.len() == 32 && s.chars().all(|c| c.is_ascii_hexdigit()) } #[cfg(test)] mod tests { use super::*; #[test] fn tenant_id_serialization_roundtrip() { let id = TenantId::new("acme-corp"); let json = serde_json::to_string(&id).unwrap(); let decoded: TenantId = serde_json::from_str(&json).unwrap(); assert_eq!(id, decoded); } #[test] fn tenant_id_default_is_empty() { let id = TenantId::default(); assert!(id.is_empty()); } #[test] fn tenant_id_is_send_sync() { fn assert_send_sync() {} assert_send_sync::(); } #[test] fn correlation_id_roundtrip_is_string() { let id = CorrelationId::new("corr-1"); let json = serde_json::to_string(&id).unwrap(); assert_eq!(json, "\"corr-1\""); let decoded: CorrelationId = serde_json::from_str(&json).unwrap(); assert_eq!(decoded.as_str(), "corr-1"); } #[test] fn trace_id_from_traceparent_parses() { let tp = "00-0123456789abcdef0123456789abcdef-1111111111111111-01"; assert_eq!( trace_id_from_traceparent(tp), Some("0123456789abcdef0123456789abcdef") ); } }