pipecircuit 0.0.12

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

pub trait Participant {
    type In<'message>;
    type Out<'message>;
}

pub enum NoInput {}

pub enum NoOutput {}

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

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

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use = "received outputs, quiescence, backpressure, and failures must be handled"]
pub enum ReceiveFromOutcome<Output, Rejected, Failure> {
    Received { output: Output },
    Progressed,
    Quiescent,
    Backpressured { rejected: Rejected },
    Failed { failure: Failure },
}

pub trait TransportFrom<Source: Participant> {
    type IncomingMessage<'message>
    where
        Self: 'message;
    type Failure;

    fn receive_from<'message>(
        &'message mut self,
        incoming_message: Self::IncomingMessage<'message>,
    ) -> ReceiveFromOutcome<Source::Out<'message>, Self::IncomingMessage<'message>, Self::Failure>;
}

pub trait DuplexFitting<ConnectedParticipant: Participant> {
    type Incoming<'fitting>: TransportFrom<ConnectedParticipant>
    where
        Self: 'fitting;
    type Outgoing<'fitting>: TransportTo<ConnectedParticipant>
    where
        Self: 'fitting;

    fn split<'fitting>(&'fitting mut self) -> (Self::Incoming<'fitting>, Self::Outgoing<'fitting>);
}

/// 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 ParticipantIn<Selected: Participant>(PhantomData<fn() -> Selected>);

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

pub struct ParticipantOut<Selected: Participant>(PhantomData<fn() -> Selected>);

impl<Selected: Participant> MessageFamily for ParticipantOut<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 Participant;

    impl super::Participant for Participant {
        type In<'message> = &'message str;
        type Out<'message> = &'message [u8];
    }

    struct Source;

    impl super::Participant for Source {
        type In<'message> = NoInput;
        type Out<'message> = &'message str;
    }

    struct Sink;

    impl super::Participant for Sink {
        type In<'message> = &'message str;
        type Out<'message> = NoOutput;
    }

    struct BorrowedInline;

    impl TransportTo<Participant> for BorrowedInline {
        type Outcome<'transport, 'message>
            = &'message str
        where
            Self: 'transport;

        fn send<'transport, 'message>(
            &'transport mut self,
            input: <Participant as super::Participant>::In<'message>,
        ) -> Self::Outcome<'transport, 'message> {
            input
        }
    }

    #[test]
    fn participant_message_families_include_uninhabited_directions() {
        fn assert_message_families<Selected>()
        where
            Selected: super::Participant,
            for<'message> ParticipantIn<Selected>:
                MessageFamily<Message<'message> = Selected::In<'message>>,
            for<'message> ParticipantOut<Selected>:
                MessageFamily<Message<'message> = Selected::Out<'message>>,
        {
        }

        assert_message_families::<Participant>();
        assert_message_families::<Source>();
        assert_message_families::<Sink>();
    }

    #[test]
    fn encoded_byte_counts_preserve_their_value() {
        assert_eq!(EncodedByteCount::new(7).get(), 7);
    }

    #[test]
    fn transports_without_internal_steps_preserve_borrowed_delivery() {
        let mut transport = BorrowedInline;
        assert_eq!(
            TransportTo::<Participant>::send(&mut transport, "borrowed"),
            "borrowed"
        );
    }
}