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/.

//! Type-erased versions of payload traits.

use crate::codec::{Codec, CodecName};
use crate::erasure::error::{ErasureError, ErasureResult};
use crate::erasure::neuron::{NeuronErased, NeuronErasedWrapper};
use crate::logging::{LogTrace, TraceContext};
use crate::payload::{Payload, PayloadRaw};
use std::any::{Any, TypeId};
use std::marker::PhantomData;
use std::sync::Arc;
use uuid::Uuid;

/// Type-erased payload that can be stored in collections with other type-erased payloads
pub trait PayloadErased: LogTrace + Send + Sync + 'static {
    fn as_any(&self) -> &dyn Any;
    fn get_trace_context(&self) -> TraceContext;
    fn get_correlation_id(&self) -> Uuid {
        self.get_trace_context().correlation_id
    }
    fn get_neuron_name(&self) -> String;
    fn get_span_id(&self) -> u64 {
        self.get_trace_context().span_id
    }
    fn get_parent_id(&self) -> Option<u64> {
        self.get_trace_context().parent_id
    }
    fn payload_type_id(&self) -> TypeId;
    fn codec_type_id(&self) -> TypeId;
    fn clone_to_box(&self) -> Box<dyn PayloadErased + Send + Sync + 'static>;
    fn clone_to_arc(&self) -> Arc<dyn PayloadErased + Send + Sync + 'static>;
    fn get_erased_neuron(&self) -> Arc<dyn NeuronErased + Send + Sync + 'static>;
    fn get_value(&self) -> &dyn std::any::Any;
}

/// Type-erased payload raw that can be stored in collections with other type-erased payload raws
pub trait PayloadRawErased: LogTrace + Send + Sync + 'static {
    fn as_any(&self) -> &dyn Any;
    fn get_bytes(&self) -> Arc<Vec<u8>>;
    fn get_trace_context(&self) -> TraceContext;
    fn get_correlation_id(&self) -> Uuid {
        self.get_trace_context().correlation_id
    }
    fn get_neuron_name(&self) -> String;
    fn get_span_id(&self) -> u64 {
        self.get_trace_context().span_id
    }
    fn get_parent_id(&self) -> Option<u64> {
        self.get_trace_context().parent_id
    }
    fn get_erased_neuron(&self) -> Arc<dyn NeuronErased + Send + Sync + 'static>;
    fn payload_type_id(&self) -> TypeId;
    fn codec_type_id(&self) -> TypeId;
    fn clone_to_box(&self) -> Box<dyn PayloadRawErased + Send + Sync + 'static>;
    fn clone_to_arc(&self) -> Arc<dyn PayloadRawErased + Send + Sync + 'static>;
}

/// A simple implementation of PayloadRawErased that doesn't require type parameters.
/// Useful for transport-level handling before the specific types are known.
#[derive(Debug)]
pub struct SimplePayloadRawErased {
    pub bytes: Arc<Vec<u8>>,
    pub neuron_name: String,
    pub trace: TraceContext,
    pub payload_type_id: TypeId,
    pub codec_type_id: TypeId,
}

impl SimplePayloadRawErased {
    pub fn new(
        bytes: Arc<Vec<u8>>,
        neuron_name: String,
        trace: TraceContext,
        payload_type_id: TypeId,
        codec_type_id: TypeId,
    ) -> Self {
        Self {
            bytes,
            neuron_name,
            trace,
            payload_type_id,
            codec_type_id,
        }
    }
}

impl PayloadRawErased for SimplePayloadRawErased {
    fn as_any(&self) -> &dyn Any {
        self
    }
    fn get_bytes(&self) -> Arc<Vec<u8>> {
        self.bytes.clone()
    }
    fn get_trace_context(&self) -> TraceContext {
        self.trace
    }
    fn get_neuron_name(&self) -> String {
        self.neuron_name.clone()
    }
    fn get_erased_neuron(&self) -> Arc<dyn NeuronErased + Send + Sync + 'static> {
        panic!("SimplePayloadRawErased does not store the neuron. This is a design limitation. In the factory-based architecture, the neuron is typically retrieved from the synapse or provided by the transport.");
    }
    fn payload_type_id(&self) -> TypeId {
        self.payload_type_id
    }
    fn codec_type_id(&self) -> TypeId {
        self.codec_type_id
    }
    fn clone_to_box(&self) -> Box<dyn PayloadRawErased + Send + Sync + 'static> {
        Box::new(Self {
            bytes: self.bytes.clone(),
            neuron_name: self.neuron_name.clone(),
            trace: self.trace,
            payload_type_id: self.payload_type_id,
            codec_type_id: self.codec_type_id,
        })
    }
    fn clone_to_arc(&self) -> Arc<dyn PayloadRawErased + Send + Sync + 'static> {
        Arc::new(Self {
            bytes: self.bytes.clone(),
            neuron_name: self.neuron_name.clone(),
            trace: self.trace,
            payload_type_id: self.payload_type_id,
            codec_type_id: self.codec_type_id,
        })
    }
}

/// Wrapper that implements PayloadErased for any concrete Payload
#[derive(Debug)]
pub struct PayloadErasedWrapper<T: 'static, C: 'static> {
    payload: Arc<Payload<T, C>>,
    _phantom: PhantomData<(T, C)>,
}

impl<T, C> PayloadErasedWrapper<T, C>
where
    T: Send + Sync + 'static,
    C: Codec<T> + CodecName + Send + Sync + 'static,
{
    pub fn new(payload: Arc<Payload<T, C>>) -> Self {
        Self {
            payload,
            _phantom: PhantomData,
        }
    }

    /// Create a type-erased payload from a correctly typed payload
    pub fn from_typed_payload(
        payload: Arc<Payload<T, C>>,
    ) -> Arc<dyn PayloadErased + Send + Sync + 'static> {
        Arc::new(Self::new(payload))
    }

    /// Get the underlying typed payload
    pub fn get_typed_payload(&self) -> Arc<Payload<T, C>> {
        self.payload.clone()
    }

    /// Get the neuron as a type-erased neuron
    pub fn get_erased_neuron(&self) -> Arc<dyn NeuronErased + Send + Sync + 'static> {
        NeuronErasedWrapper::from_typed_neuron(self.payload.neuron.clone())
    }

    /// Convert this wrapper to a correctly typed payload with different type parameters
    /// Returns an error if the type parameters don't match
    pub fn to_typed_payload<U, D>(&self) -> ErasureResult<Arc<Payload<U, D>>>
    where
        U: Send + Sync + 'static,
        D: Send + Sync + 'static,
    {
        // Check if the type parameters match using Any downcast (safest)
        if let Some(wrapper) = (self as &dyn Any).downcast_ref::<PayloadErasedWrapper<U, D>>() {
            Ok(wrapper.payload.clone())
        } else {
            Err(ErasureError::PayloadTypeMismatch {
                expected_payload_type: TypeId::of::<U>(),
                expected_codec_type: TypeId::of::<D>(),
                actual_payload_type: TypeId::of::<T>(),
                actual_codec_type: TypeId::of::<C>(),
            })
        }
    }
}

impl<T, C> PayloadErased for PayloadErasedWrapper<T, C>
where
    T: Send + Sync + 'static,
    C: Codec<T> + CodecName + Send + Sync + 'static,
{
    fn as_any(&self) -> &dyn Any {
        self
    }

    fn get_trace_context(&self) -> TraceContext {
        self.payload.trace
    }

    fn get_neuron_name(&self) -> String {
        self.payload.neuron.name()
    }

    fn payload_type_id(&self) -> TypeId {
        TypeId::of::<T>()
    }

    fn codec_type_id(&self) -> TypeId {
        TypeId::of::<C>()
    }

    fn clone_to_box(&self) -> Box<dyn PayloadErased + Send + Sync + 'static> {
        Box::new(PayloadErasedWrapper {
            payload: self.payload.clone(),
            _phantom: PhantomData,
        })
    }

    fn clone_to_arc(&self) -> Arc<dyn PayloadErased + Send + Sync + 'static> {
        Arc::new(PayloadErasedWrapper {
            payload: self.payload.clone(),
            _phantom: PhantomData,
        })
    }

    fn get_erased_neuron(&self) -> Arc<dyn NeuronErased + Send + Sync + 'static> {
        NeuronErasedWrapper::from_typed_neuron(self.payload.neuron.clone())
    }

    fn get_value(&self) -> &dyn std::any::Any {
        &*self.payload.value as &dyn std::any::Any
    }
}

/// Wrapper that implements PayloadRawErased for PayloadRaw
#[derive(Debug)]
pub struct PayloadRawErasedWrapper<T: 'static, C: 'static> {
    payload: Arc<PayloadRaw<T, C>>,
    _phantom: PhantomData<(T, C)>,
}

impl<T, C> PayloadRawErasedWrapper<T, C>
where
    T: Send + Sync + 'static,
    C: Codec<T> + CodecName + Send + Sync + 'static,
{
    pub fn new(payload: Arc<PayloadRaw<T, C>>) -> Self {
        Self {
            payload,
            _phantom: PhantomData,
        }
    }

    /// Create a type-erased payload raw from a payload raw
    pub fn from_payload_raw(
        payload: Arc<PayloadRaw<T, C>>,
    ) -> Arc<dyn PayloadRawErased + Send + Sync + 'static> {
        Arc::new(Self::new(payload))
    }

    /// Get the underlying payload raw
    pub fn get_payload_raw(&self) -> Arc<PayloadRaw<T, C>> {
        self.payload.clone()
    }

    /// Convert this wrapper to a correctly typed payload raw
    pub fn to_typed_payload(&self) -> Arc<PayloadRaw<T, C>> {
        self.payload.clone()
    }
}

impl<T, C> PayloadRawErased for PayloadRawErasedWrapper<T, C>
where
    T: Send + Sync + 'static,
    C: Codec<T> + CodecName + Send + Sync + 'static,
{
    fn as_any(&self) -> &dyn Any {
        self
    }

    fn get_bytes(&self) -> Arc<Vec<u8>> {
        self.payload.value.clone()
    }

    fn get_trace_context(&self) -> TraceContext {
        self.payload.trace
    }

    fn get_neuron_name(&self) -> String {
        self.payload.neuron.name()
    }

    fn get_erased_neuron(&self) -> Arc<dyn NeuronErased + Send + Sync + 'static> {
        NeuronErasedWrapper::from_typed_neuron(self.payload.neuron.clone())
    }

    fn payload_type_id(&self) -> TypeId {
        TypeId::of::<T>()
    }

    fn codec_type_id(&self) -> TypeId {
        TypeId::of::<C>()
    }

    fn clone_to_box(&self) -> Box<dyn PayloadRawErased + Send + Sync + 'static> {
        Box::new(PayloadRawErasedWrapper {
            payload: self.payload.clone(),
            _phantom: PhantomData,
        })
    }

    fn clone_to_arc(&self) -> Arc<dyn PayloadRawErased + Send + Sync + 'static> {
        Arc::new(PayloadRawErasedWrapper {
            payload: self.payload.clone(),
            _phantom: PhantomData,
        })
    }
}

/// Convenience function to create a type-erased payload from a correctly typed payload
pub fn erase_payload<T, C>(
    payload: Arc<Payload<T, C>>,
) -> Arc<dyn PayloadErased + Send + Sync + 'static>
where
    T: Send + Sync + 'static,
    C: Codec<T> + CodecName + Send + Sync + 'static,
{
    PayloadErasedWrapper::from_typed_payload(payload)
}

/// Convenience function to create a type-erased payload raw from a payload raw
pub fn erase_payload_raw<T, C>(
    payload: Arc<PayloadRaw<T, C>>,
) -> Arc<dyn PayloadRawErased + Send + Sync + 'static>
where
    T: Send + Sync + 'static,
    C: Codec<T> + CodecName + Send + Sync + 'static,
{
    PayloadRawErasedWrapper::from_payload_raw(payload)
}

/// Convenience function to convert a type-erased payload wrapper back to a correctly typed payload
pub fn unerase_payload<T, C>(wrapper: &PayloadErasedWrapper<T, C>) -> Arc<Payload<T, C>>
where
    T: Send + Sync + 'static,
    C: Codec<T> + CodecName + Send + Sync + 'static,
{
    wrapper.get_typed_payload()
}

/// Convenience function to convert a type-erased payload raw wrapper back to a payload raw
pub fn unerase_payload_raw<T, C>(wrapper: &PayloadRawErasedWrapper<T, C>) -> Arc<PayloadRaw<T, C>>
where
    T: Send + Sync + 'static,
    C: Codec<T> + CodecName + Send + Sync + 'static,
{
    wrapper.get_payload_raw()
}