Files
cloudlysis/shared/src/lib.rs
Vlad Durnea 1298d9a3df
Some checks failed
ci / rust (push) Failing after 2m34s
ci / ui (push) Failing after 30s
Monorepo consolidation: workspace, shared types, transport plans, docker/swam assets
2026-03-30 11:40:42 +03:00

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")
);
}
}