arcly-http 0.2.2

Enterprise-grade NestJS-inspired web framework on axum: zero-lock DI, declarative controllers, multi-tenant data routing, transactional outbox, ABAC, and a self-documenting OpenAPI surface
Documentation
//! W3C Trace Context propagation — the single place incoming `traceparent`
//! headers are parsed and per-hop span IDs are minted.
//!
//! Every request boundary (HTTP macro routes, plugin routes) calls
//! [`extract_trace_context`]; outgoing propagation is handled by
//! `RequestContext::traceparent()`.

use axum::http::HeaderMap;

use crate::observability::lean_telemetry::{new_span_id, new_trace_id, parse_traceparent};

/// Distributed-tracing identity for one server hop.
pub struct TraceContext {
    /// Preserved across the entire distributed call chain.
    pub trace_id: [u8; 16],
    /// This hop's freshly-minted span ID.
    pub span_id: [u8; 8],
    /// The upstream caller's span ID (all-zeros = this hop is the root).
    pub parent_span_id: [u8; 8],
}

/// Continue the caller's trace if a valid `traceparent` header is present;
/// otherwise start a new root trace.
pub fn extract_trace_context(headers: &HeaderMap) -> TraceContext {
    let (trace_id, parent_span_id) = headers
        .get("traceparent")
        .and_then(|h| parse_traceparent(h.as_bytes()))
        .map(|t| (t.trace_id, t.span_id))
        .unwrap_or_else(|| (new_trace_id(), [0u8; 8]));

    TraceContext {
        trace_id,
        span_id: new_span_id(),
        parent_span_id,
    }
}

impl TraceContext {
    /// W3C `traceparent` string for embedding in non-HTTP envelopes
    /// (outbox rows, queue messages) — `00-{trace}-{span}-01` where the
    /// span field is THIS hop's span (the consumer's parent).
    pub fn to_traceparent(&self) -> String {
        format!(
            "00-{}-{}-01",
            crate::observability::lean_telemetry::hex_encode(&self.trace_id),
            crate::observability::lean_telemetry::hex_encode(&self.span_id),
        )
    }

    /// Continue a trace carried in a message envelope: same trace ID, the
    /// producer's span becomes this hop's parent, and a fresh span is minted
    /// for the consumer side — so async hops chain in the trace UI instead
    /// of starting orphan roots.
    pub fn from_traceparent(s: &str) -> Option<TraceContext> {
        let parsed = crate::observability::lean_telemetry::parse_traceparent(s.as_bytes())?;
        Some(TraceContext {
            trace_id: parsed.trace_id,
            span_id: crate::observability::lean_telemetry::new_span_id(),
            parent_span_id: parsed.span_id,
        })
    }

    /// A brand-new root trace (for messages with no carried context).
    pub fn new_root() -> TraceContext {
        TraceContext {
            trace_id: crate::observability::lean_telemetry::new_trace_id(),
            span_id: crate::observability::lean_telemetry::new_span_id(),
            parent_span_id: [0u8; 8],
        }
    }
}