pipecircuit 0.0.2

Correct. Robust. Fast. Portable.
Documentation
use core::marker::PhantomData;

/// Declares the values that may enter one participant.
///
/// The lifetime-indexed family permits a participant to accept values borrowed
/// for one routing turn without requiring every transport to own them.
pub trait InPipe {
    type In<'message>;
}

/// Declares the values that may leave one participant.
///
/// Input and output pipes are intentionally independent. A participant may
/// expose either direction without inventing a meaningless opposite one.
pub trait OutPipe {
    type Out<'message>;
}

/// Moves one value into a particular semantic input pipe.
///
/// The destination pipe is part of the contract so an implementation can keep
/// distinct destinations nominal even when their payload types are equal.
/// Outcomes remain precise: inline transport completes a step now, while a
/// queued transport may instead report admission, backpressure, or failure.
pub trait Transport<Destination: InPipe> {
    type Outcome<'transport, 'message>
    where
        Self: 'transport;

    #[must_use = "transport outcomes must be routed or settled"]
    fn transport<'transport, 'message>(
        &'transport mut self,
        input: Destination::In<'message>,
    ) -> Self::Outcome<'transport, 'message>;
}

/// A lifetime-indexed family of values that may be encoded or decoded.
pub trait MessageFamily {
    type Message<'message>;
}

/// Selects the message family accepted by an input pipe.
pub struct InPipeMessages<Selected: InPipe>(PhantomData<fn() -> Selected>);

impl<Selected: InPipe> MessageFamily for InPipeMessages<Selected> {
    type Message<'message> = Selected::In<'message>;
}

/// Selects the message family emitted by an output pipe.
pub struct OutPipeMessages<Selected: OutPipe>(PhantomData<fn() -> Selected>);

impl<Selected: OutPipe> MessageFamily for OutPipeMessages<Selected> {
    type Message<'message> = Selected::Out<'message>;
}

/// Selects one owned message type as a lifetime-indexed family.
pub struct OwnedMessage<Message>(PhantomData<fn() -> Message>);

impl<Message> MessageFamily for OwnedMessage<Message> {
    type Message<'message> = Message;
}

/// The byte count written by a message encoder.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct EncodedByteCount(usize);

impl EncodedByteCount {
    pub const fn new(value: usize) -> Self {
        Self(value)
    }

    pub const fn get(self) -> usize {
        self.0
    }
}

/// Proves that one lifetime-indexed message family can be serialized into
/// bounded caller-owned storage.
pub trait MessageEncoder {
    type Message<'message>: ?Sized;
    type Error;

    fn encode<'message>(
        &self,
        message: &Self::Message<'message>,
        destination: &mut [u8],
    ) -> Result<EncodedByteCount, Self::Error>;
}

/// Proves that one serialized message can become a typed value.
///
/// The decoded value may borrow from the reusable receive frame.
pub trait MessageDecoder {
    type Message<'frame>;
    type Error;

    fn decode<'frame>(&self, frame: &'frame mut [u8])
    -> Result<Self::Message<'frame>, Self::Error>;
}

#[cfg(test)]
mod tests {
    use super::*;

    struct ParticipantInPipe;

    impl InPipe for ParticipantInPipe {
        type In<'message> = &'message str;
    }

    struct ParticipantOutPipe;

    impl OutPipe for ParticipantOutPipe {
        type Out<'message> = &'message [u8];
    }

    #[test]
    fn directional_pipe_message_families_remain_independent() {
        fn accepts_input_only<Input: InPipe>() {}
        fn emits_output_only<Output: OutPipe>() {}

        fn assert_message_families<Input, Output>()
        where
            Input: InPipe,
            Output: OutPipe,
            for<'message> InPipeMessages<Input>:
                MessageFamily<Message<'message> = Input::In<'message>>,
            for<'message> OutPipeMessages<Output>:
                MessageFamily<Message<'message> = Output::Out<'message>>,
        {
        }

        accepts_input_only::<ParticipantInPipe>();
        emits_output_only::<ParticipantOutPipe>();
        assert_message_families::<ParticipantInPipe, ParticipantOutPipe>();
    }
}