197 lines
4.7 KiB
Rust
197 lines
4.7 KiB
Rust
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<String>) -> 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<Self, Self::Err> {
|
|
Ok(Self(s.to_string()))
|
|
}
|
|
}
|
|
|
|
impl AsRef<str> 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<String>) -> 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<Self, Self::Err> {
|
|
Ok(Self(s.to_string()))
|
|
}
|
|
}
|
|
|
|
impl AsRef<str> 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<String>) -> 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<Self, Self::Err> {
|
|
Ok(Self(s.to_string()))
|
|
}
|
|
}
|
|
|
|
impl AsRef<str> 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<String> {
|
|
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<T: Send + Sync>() {}
|
|
assert_send_sync::<TenantId>();
|
|
}
|
|
|
|
#[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")
|
|
);
|
|
}
|
|
}
|