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::codec::{Codec, CodecName};
use crate::logging::TraceContext;
use crate::neuron::Neuron;
use std::sync::Arc;
use uuid::Uuid;

/// A typed payload containing a value, its associated neuron, and tracing context.
///
/// # Examples
///
/// ```rust
/// # use std::sync::Arc;
/// # use plexor_core::payload::Payload;
/// # use plexor_core::neuron::{Neuron, NeuronImpl};
/// # use plexor_core::namespace::NamespaceImpl;
/// # use plexor_core::codec::{Codec, CodecError, CodecName};
/// #
/// # #[derive(Debug, Clone, PartialEq)]
/// # struct Dummy(u32);
/// # impl CodecName for Dummy { fn name() -> &'static str { "dummy" } }
/// # impl Codec<Dummy> for Dummy {
/// #     fn encode(_: &Dummy) -> Result<Vec<u8>, CodecError> { Ok(vec![]) }
/// #     fn decode(_: &[u8]) -> Result<Dummy, CodecError> { Ok(Dummy(0)) }
/// # }
/// #
/// # let ns = Arc::new(NamespaceImpl { delimiter: ".", parts: vec!["test"] });
/// # let neuron = Arc::new(NeuronImpl::<Dummy, Dummy>::new(ns));
/// #
/// let payload = Payload::new(Dummy(42), neuron);
/// assert_eq!(*payload.value, Dummy(42));
/// ```
#[derive(Debug)]
pub struct Payload<T, C> {
    pub value: Arc<T>,
    pub neuron: Arc<dyn Neuron<T, C> + Send + Sync>,
    pub trace: TraceContext,
}

impl<T, C> Payload<T, C> {
    pub fn from_parts(
        value: Arc<T>,
        neuron: Arc<dyn Neuron<T, C> + Send + Sync>,
        trace: TraceContext,
    ) -> Self {
        Self {
            value,
            neuron,
            trace,
        }
    }

    /// Helper to create a new Payload with default tracing fields (new span_id, no parent_id).
    /// Used when initiating a new signal.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use std::sync::Arc;
    /// # use plexor_core::payload::Payload;
    /// # use plexor_core::neuron::{Neuron, NeuronImpl};
    /// # use plexor_core::namespace::NamespaceImpl;
    /// # use plexor_core::codec::{Codec, CodecError, CodecName};
    /// #
    /// # #[derive(Debug, Clone, PartialEq)]
    /// # struct Dummy(u32);
    /// # impl CodecName for Dummy { fn name() -> &'static str { "dummy" } }
    /// # impl Codec<Dummy> for Dummy {
    /// #     fn encode(_: &Dummy) -> Result<Vec<u8>, CodecError> { Ok(vec![]) }
    /// #     fn decode(_: &[u8]) -> Result<Dummy, CodecError> { Ok(Dummy(0)) }
    /// # }
    /// #
    /// # let ns = Arc::new(NamespaceImpl { delimiter: ".", parts: vec!["test"] });
    /// # let neuron = Arc::new(NeuronImpl::<Dummy, Dummy>::new(ns));
    /// # use uuid::Uuid;
    /// #
    /// let correlation_id = Uuid::now_v7();
    /// let payload = Payload::with_correlation(Dummy(42), neuron, Some(correlation_id));
    /// assert_eq!(payload.correlation_id(), correlation_id);
    /// ```
    pub fn with_correlation(
        value: T,
        neuron: Arc<dyn Neuron<T, C> + Send + Sync>,
        correlation_id: Option<Uuid>,
    ) -> Arc<Self>
    where
        T: Send + Sync + 'static,
        C: Codec<T> + CodecName + Send + Sync + 'static,
    {
        let correlation = correlation_id.unwrap_or_else(Uuid::now_v7);
        // New trace: span_id is lower 64 bits of correlation_id
        let span_id = correlation.as_u128() as u64;

        Arc::new(Payload::from_parts(
            Arc::new(value),
            neuron,
            TraceContext::from_parts(correlation, span_id, None),
        ))
    }

    /// Simplified creation method for new signals.
    /// automatically generates correlation_id and other tracing fields.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use std::sync::Arc;
    /// # use plexor_core::payload::Payload;
    /// # use plexor_core::neuron::{Neuron, NeuronImpl};
    /// # use plexor_core::namespace::NamespaceImpl;
    /// # use plexor_core::codec::{Codec, CodecError, CodecName};
    /// #
    /// # #[derive(Debug, Clone, PartialEq)]
    /// # struct Dummy { foo: i32 }
    /// # impl CodecName for Dummy { fn name() -> &'static str { "dummy" } }
    /// # impl Codec<Dummy> for Dummy {
    /// #     fn encode(_: &Dummy) -> Result<Vec<u8>, CodecError> { Ok(vec![]) }
    /// #     fn decode(_: &[u8]) -> Result<Dummy, CodecError> { Ok(Dummy { foo: 42 }) }
    /// # }
    /// #
    /// let ns = Arc::new(NamespaceImpl { delimiter: ".", parts: vec!["test"] });
    /// let neuron = Arc::new(NeuronImpl::<Dummy, Dummy>::new(ns));
    /// let data = Dummy { foo: 123 };
    ///
    /// let payload = Payload::new(data.clone(), neuron);
    /// assert_eq!(*payload.value, data);
    /// ```
    pub fn new(value: T, neuron: Arc<dyn Neuron<T, C> + Send + Sync>) -> Arc<Self>
    where
        T: Send + Sync + 'static,
        C: Codec<T> + CodecName + Send + Sync + 'static,
    {
        Self::with_correlation(value, neuron, None)
    }

    /// Create a builder for constructing a Payload.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use std::sync::Arc;
    /// # use plexor_core::payload::Payload;
    /// # use plexor_core::neuron::{Neuron, NeuronImpl};
    /// # use plexor_core::namespace::NamespaceImpl;
    /// # use plexor_core::codec::{Codec, CodecError, CodecName};
    /// #
    /// # #[derive(Debug, Clone, PartialEq)]
    /// # struct Dummy(u32);
    /// # impl CodecName for Dummy { fn name() -> &'static str { "dummy" } }
    /// # impl Codec<Dummy> for Dummy {
    /// #     fn encode(_: &Dummy) -> Result<Vec<u8>, CodecError> { Ok(vec![]) }
    /// #     fn decode(_: &[u8]) -> Result<Dummy, CodecError> { Ok(Dummy(0)) }
    /// # }
    /// #
    /// # let ns = Arc::new(NamespaceImpl { delimiter: ".", parts: vec!["test"] });
    /// # let neuron = Arc::new(NeuronImpl::<Dummy, Dummy>::new(ns));
    /// #
    /// let payload = Payload::builder()
    ///     .value(Dummy(42))
    ///     .neuron(neuron)
    ///     .build()
    ///     .unwrap();
    /// assert_eq!(*payload.value, Dummy(42));
    /// ```
    pub fn builder() -> PayloadBuilder<T, C> {
        PayloadBuilder::default()
    }

    pub fn correlation_id(&self) -> Uuid {
        self.trace.correlation_id
    }

    pub fn span_id(&self) -> u64 {
        self.trace.span_id
    }

    pub fn parent_id(&self) -> Option<u64> {
        self.trace.parent_id
    }
}

pub struct PayloadBuilder<T, C> {
    value: Option<T>,
    correlation_id: Option<Uuid>,
    neuron: Option<Arc<dyn Neuron<T, C> + Send + Sync>>,
    span_id: Option<u64>,
    parent_id: Option<u64>,
}

impl<T, C> Default for PayloadBuilder<T, C> {
    fn default() -> Self {
        Self {
            value: None,
            correlation_id: None,
            neuron: None,
            span_id: None,
            parent_id: None,
        }
    }
}

impl<T, C> PayloadBuilder<T, C> {
    pub fn value(mut self, value: T) -> Self {
        self.value = Some(value);
        self
    }

    pub fn correlation_id(mut self, id: Uuid) -> Self {
        self.correlation_id = Some(id);
        self
    }

    pub fn neuron(mut self, neuron: Arc<dyn Neuron<T, C> + Send + Sync>) -> Self {
        self.neuron = Some(neuron);
        self
    }

    pub fn span_id(mut self, id: u64) -> Self {
        self.span_id = Some(id);
        self
    }

    pub fn parent_id(mut self, id: u64) -> Self {
        self.parent_id = Some(id);
        self
    }

    pub fn build(self) -> Result<Arc<Payload<T, C>>, String>
    where
        T: Send + Sync + 'static,
        C: Codec<T> + CodecName + Send + Sync + 'static,
    {
        let value = self.value.ok_or("Value is required")?;
        let neuron = self.neuron.ok_or("Neuron is required")?;
        let correlation = self.correlation_id.unwrap_or_else(Uuid::now_v7);

        let span = self.span_id.unwrap_or_else(|| correlation.as_u128() as u64);

        Ok(Arc::new(Payload {
            value: Arc::new(value),
            neuron,
            trace: TraceContext::from_parts(correlation, span, self.parent_id),
        }))
    }
}

/// A type-erased raw payload containing bytes, its associated neuron, and tracing context.
///
/// # Examples
///
/// ```rust
/// # use std::sync::Arc;
/// # use plexor_core::payload::PayloadRaw;
/// # use plexor_core::neuron::{Neuron, NeuronImpl};
/// # use plexor_core::namespace::NamespaceImpl;
/// # use plexor_core::codec::{Codec, CodecError, CodecName};
/// #
/// # #[derive(Debug, Clone, PartialEq)]
/// # struct Dummy;
/// # impl CodecName for Dummy { fn name() -> &'static str { "dummy" } }
/// # impl Codec<Dummy> for Dummy {
/// #     fn encode(_: &Dummy) -> Result<Vec<u8>, CodecError> { Ok(vec![1, 2, 3]) }
/// #     fn decode(_: &[u8]) -> Result<Dummy, CodecError> { Ok(Dummy) }
/// # }
/// #
/// # let ns = Arc::new(NamespaceImpl { delimiter: ".", parts: vec!["test"] });
/// # let neuron = Arc::new(NeuronImpl::<Dummy, Dummy>::new(ns));
/// #
/// let payload = PayloadRaw::new(vec![1, 2, 3], neuron);
/// assert_eq!(*payload.value, vec![1, 2, 3]);
/// ```
#[derive(Debug)]
pub struct PayloadRaw<T, C> {
    pub value: Arc<Vec<u8>>,
    pub neuron: Arc<dyn Neuron<T, C> + Send + Sync>,
    pub trace: TraceContext,
}

impl<T, C> PayloadRaw<T, C> {
    pub fn from_parts(
        value: Arc<Vec<u8>>,
        neuron: Arc<dyn Neuron<T, C> + Send + Sync>,
        trace: TraceContext,
    ) -> Self {
        Self {
            value,
            neuron,
            trace,
        }
    }

    /// Helper to create a new PayloadRaw with default tracing fields (new span_id, no parent_id).
    /// Used when initiating a new signal where payload is already raw bytes.
    pub fn with_correlation(
        value: Vec<u8>,
        neuron: Arc<dyn Neuron<T, C> + Send + Sync>,
        correlation_id: Option<Uuid>,
    ) -> Arc<Self>
    where
        T: Send + Sync + 'static,
        C: Codec<T> + CodecName + Send + Sync + 'static,
    {
        let correlation = correlation_id.unwrap_or_else(Uuid::now_v7);
        let span_id = correlation.as_u128() as u64;

        Arc::new(PayloadRaw::from_parts(
            Arc::new(value),
            neuron,
            TraceContext::from_parts(correlation, span_id, None),
        ))
    }

    /// # Examples
    ///
    /// ```rust
    /// # use std::sync::Arc;
    /// # use plexor_core::payload::PayloadRaw;
    /// # use plexor_core::neuron::{Neuron, NeuronImpl};
    /// # use plexor_core::namespace::NamespaceImpl;
    /// # use plexor_core::codec::{Codec, CodecError, CodecName};
    /// #
    /// # #[derive(Debug, Clone)]
    /// # struct Dummy;
    /// # impl CodecName for Dummy { fn name() -> &'static str { "dummy" } }
    /// # impl Codec<Dummy> for Dummy {
    /// #     fn encode(_: &Dummy) -> Result<Vec<u8>, CodecError> { Ok(vec![]) }
    /// #     fn decode(_: &[u8]) -> Result<Dummy, CodecError> { Ok(Dummy) }
    /// # }
    /// #
    /// let ns = Arc::new(NamespaceImpl { delimiter: ".", parts: vec!["test"] });
    /// let neuron = Arc::new(NeuronImpl::<Dummy, Dummy>::new(ns));
    /// let raw_data = vec![1, 2, 3];
    ///
    /// let payload = PayloadRaw::new(raw_data.clone(), neuron);
    /// assert_eq!(*payload.value, raw_data);
    /// ```
    pub fn new(value: Vec<u8>, neuron: Arc<dyn Neuron<T, C> + Send + Sync>) -> Arc<Self>
    where
        T: Send + Sync + 'static,
        C: Codec<T> + CodecName + Send + Sync + 'static,
    {
        Self::with_correlation(value, neuron, None)
    }

    pub fn correlation_id(&self) -> Uuid {
        self.trace.correlation_id
    }

    pub fn span_id(&self) -> u64 {
        self.trace.span_id
    }

    pub fn parent_id(&self) -> Option<u64> {
        self.trace.parent_id
    }
}