plexor-core 0.1.0-alpha.2

Core library for the rust implementation of the Plexo distributed system architecture, providing the fundamental Plexus, Neuron, Codec, and Axon abstractions.
Documentation
// Copyright 2025 Alecks Gates
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use crate::payload::{Payload, PayloadRaw};
use std::sync::Arc;
use uuid::Uuid;

/// A standalone container for tracing context.
/// Useful for initializing a trace before passing it to an Axon.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct TraceContext {
    pub correlation_id: Uuid,
    pub span_id: u64,
    pub parent_id: Option<u64>,
}

impl TraceContext {
    /// Start a new root trace.
    pub fn new() -> Self {
        let correlation_id = Uuid::now_v7();
        let span_id = correlation_id.as_u128() as u64; // Deterministic first span
        Self {
            correlation_id,
            span_id,
            parent_id: None,
        }
    }

    /// Create a child context from this one.
    pub fn new_child(&self) -> Self {
        Self {
            correlation_id: self.correlation_id,
            span_id: Uuid::now_v7().as_u128() as u64, // New random span ID
            parent_id: Some(self.span_id),
        }
    }

    /// Create a context from explicit parts.
    pub fn from_parts(correlation_id: Uuid, span_id: u64, parent_id: Option<u64>) -> Self {
        Self {
            correlation_id,
            span_id,
            parent_id,
        }
    }
}

impl Default for TraceContext {
    fn default() -> Self {
        Self::new()
    }
}

/// Trait for types that can provide tracing context for logging.
pub trait LogTrace {
    fn correlation_id(&self) -> Uuid;
    fn span_id(&self) -> u64;
    fn parent_id(&self) -> Option<u64>;
    fn neuron_name(&self) -> String;

    /// Returns the full trace context.
    fn trace_context(&self) -> TraceContext {
        TraceContext::from_parts(self.correlation_id(), self.span_id(), self.parent_id())
    }

    /// Returns a tracing span with the tracing fields attached at INFO level.
    fn span_info(&self, name: &'static str) -> tracing::Span {
        let correlation_id = self.correlation_id().to_string();
        let span_id = self.span_id().to_string();
        let parent_id = self
            .parent_id()
            .map(|id| id.to_string())
            .unwrap_or_default();
        let neuron = self.neuron_name();

        tracing::info_span!(
            target: "plexor::trace",
            "plexor",
            log_name = name,
            correlation_id = %correlation_id,
            span_id = %span_id,
            parent_id = %parent_id,
            neuron = %neuron
        )
    }

    /// Returns a tracing span with the tracing fields attached at DEBUG level.
    fn span_debug(&self, name: &'static str) -> tracing::Span {
        let correlation_id = self.correlation_id().to_string();
        let span_id = self.span_id().to_string();
        let parent_id = self
            .parent_id()
            .map(|id| id.to_string())
            .unwrap_or_default();
        let neuron = self.neuron_name();

        tracing::debug_span!(
            target: "plexor::trace",
            "plexor",
            log_name = name,
            correlation_id = %correlation_id,
            span_id = %span_id,
            parent_id = %parent_id,
            neuron = %neuron
        )
    }
}

impl LogTrace for TraceContext {
    fn correlation_id(&self) -> Uuid {
        self.correlation_id
    }
    fn span_id(&self) -> u64 {
        self.span_id
    }
    fn parent_id(&self) -> Option<u64> {
        self.parent_id
    }
    fn neuron_name(&self) -> String {
        "app".to_string()
    }
    fn trace_context(&self) -> TraceContext {
        *self
    }
}

impl LogTrace for Uuid {
    fn correlation_id(&self) -> Uuid {
        Uuid::nil()
    }
    fn span_id(&self) -> u64 {
        0
    }
    fn parent_id(&self) -> Option<u64> {
        None
    }
    fn neuron_name(&self) -> String {
        "none".to_string()
    }

    fn span_info(&self, name: &'static str) -> tracing::Span {
        tracing::info_span!(
            target: "plexor::trace",
            "plexor",
            log_name = name,
            ganglion_id = %self
        )
    }

    fn span_debug(&self, name: &'static str) -> tracing::Span {
        tracing::debug_span!(
            target: "plexor::trace",
            "plexor",
            log_name = name,
            ganglion_id = %self
        )
    }
}

impl<T, C> LogTrace for Payload<T, C>
where
    T: Send + Sync + 'static,
    C: crate::codec::Codec<T> + crate::codec::CodecName + Send + Sync + 'static,
{
    fn correlation_id(&self) -> Uuid {
        self.trace.correlation_id
    }
    fn span_id(&self) -> u64 {
        self.trace.span_id
    }
    fn parent_id(&self) -> Option<u64> {
        self.trace.parent_id
    }
    fn neuron_name(&self) -> String {
        self.neuron.name()
    }
    fn trace_context(&self) -> TraceContext {
        self.trace
    }
}

impl<T, C> LogTrace for PayloadRaw<T, C>
where
    T: Send + Sync + 'static,
    C: crate::codec::Codec<T> + crate::codec::CodecName + Send + Sync + 'static,
{
    fn correlation_id(&self) -> Uuid {
        self.trace.correlation_id
    }
    fn span_id(&self) -> u64 {
        self.trace.span_id
    }
    fn parent_id(&self) -> Option<u64> {
        self.trace.parent_id
    }
    fn neuron_name(&self) -> String {
        self.neuron.name()
    }
    fn trace_context(&self) -> TraceContext {
        self.trace
    }
}

impl LogTrace for crate::erasure::payload::SimplePayloadRawErased {
    fn correlation_id(&self) -> Uuid {
        self.trace.correlation_id
    }
    fn span_id(&self) -> u64 {
        self.trace.span_id
    }
    fn parent_id(&self) -> Option<u64> {
        self.trace.parent_id
    }
    fn neuron_name(&self) -> String {
        self.neuron_name.clone()
    }
    fn trace_context(&self) -> TraceContext {
        self.trace
    }
}

impl<T, C> LogTrace for crate::erasure::payload::PayloadErasedWrapper<T, C>
where
    T: Send + Sync + 'static,
    C: crate::codec::Codec<T> + crate::codec::CodecName + Send + Sync + 'static,
{
    fn correlation_id(&self) -> Uuid {
        self.get_typed_payload().trace.correlation_id
    }
    fn span_id(&self) -> u64 {
        self.get_typed_payload().trace.span_id
    }
    fn parent_id(&self) -> Option<u64> {
        self.get_typed_payload().trace.parent_id
    }
    fn neuron_name(&self) -> String {
        self.get_typed_payload().neuron.name()
    }
    fn trace_context(&self) -> TraceContext {
        self.get_typed_payload().trace
    }
}

impl<T, C> LogTrace for crate::erasure::payload::PayloadRawErasedWrapper<T, C>
where
    T: Send + Sync + 'static,
    C: crate::codec::Codec<T> + crate::codec::CodecName + Send + Sync + 'static,
{
    fn correlation_id(&self) -> Uuid {
        self.get_payload_raw().trace.correlation_id
    }
    fn span_id(&self) -> u64 {
        self.get_payload_raw().trace.span_id
    }
    fn parent_id(&self) -> Option<u64> {
        self.get_payload_raw().trace.parent_id
    }
    fn neuron_name(&self) -> String {
        self.get_payload_raw().neuron.name()
    }
    fn trace_context(&self) -> TraceContext {
        self.get_payload_raw().trace
    }
}

impl<P: LogTrace + ?Sized> LogTrace for Arc<P> {
    fn correlation_id(&self) -> Uuid {
        (**self).correlation_id()
    }
    fn span_id(&self) -> u64 {
        (**self).span_id()
    }
    fn parent_id(&self) -> Option<u64> {
        (**self).parent_id()
    }
    fn neuron_name(&self) -> String {
        (**self).neuron_name()
    }
}