pipecircuit 0.0.6

Correct. Robust. Fast. Portable.
Documentation
use core::any::type_name;

use crate::{
    NoopTurnObserver, Participant, StateMachine, TurnContext, TurnObservation, TurnObserver,
};

pub trait CircuitInputOf<Machine: StateMachine> {
    type Outcome;

    #[must_use = "circuit input outcomes must be delivered or settled"]
    fn turn<Observer: TurnObserver>(
        self,
        circuit: &mut Machine,
        turn: &mut TurnContext<Observer>,
    ) -> Self::Outcome;
}

mod private {
    use super::{CircuitInputOf, Participant, StateMachine};

    pub(super) trait TurnLaw<'message> {}

    impl<'message, Machine> TurnLaw<'message> for Machine
    where
        Machine: StateMachine + Participant,
        Machine::In<'message>: CircuitInputOf<Machine, Outcome = Machine::Out<'message>>,
    {
    }
}

#[doc(hidden)]
#[allow(private_bounds)]
pub trait TurnsCircuitInputToOutput<'message>:
    StateMachine + Participant + private::TurnLaw<'message>
{
    #[must_use = "circuit turn outcomes must be delivered or settled"]
    #[doc(hidden)]
    fn turn_boundary<Observer: TurnObserver>(
        &mut self,
        input: Self::In<'message>,
        turn: &mut TurnContext<Observer>,
    ) -> Self::Out<'message>;
}

impl<'message, Machine> TurnsCircuitInputToOutput<'message> for Machine
where
    Machine: StateMachine + Participant,
    Machine::In<'message>: CircuitInputOf<Machine, Outcome = Machine::Out<'message>>,
{
    #[inline(always)]
    fn turn_boundary<Observer: TurnObserver>(
        &mut self,
        input: Self::In<'message>,
        turn: &mut TurnContext<Observer>,
    ) -> Self::Out<'message> {
        input.turn(self, turn)
    }
}

pub trait Circuit:
    StateMachine + Participant + for<'message> TurnsCircuitInputToOutput<'message>
{
    type Switchboard: for<'message> crate::Switchboard<
            BoundaryIn<'message> = Self::In<'message>,
            BoundaryOut<'message> = Self::Out<'message>,
        >;

    fn switchboard(&self) -> &Self::Switchboard;

    fn switchboard_mut(&mut self) -> &mut Self::Switchboard;

    #[must_use = "circuit turn outcomes must be delivered or settled"]
    #[inline(always)]
    fn turn<'message>(&mut self, input: Self::In<'message>) -> Self::Out<'message> {
        self.turn_in_context(input, &mut TurnContext::new(NoopTurnObserver))
    }

    #[must_use = "circuit turn outcomes must be delivered or settled"]
    #[doc(hidden)]
    #[inline(always)]
    fn turn_in_context<'message, Observer: TurnObserver>(
        &mut self,
        input: Self::In<'message>,
        turn: &mut TurnContext<Observer>,
    ) -> Self::Out<'message> {
        turn.observer_mut()
            .observe(TurnObservation::CircuitTurnBegan {
                circuit: type_name::<Self>(),
                input: type_name::<Self::In<'message>>(),
            });
        let output =
            <Self as TurnsCircuitInputToOutput<'message>>::turn_boundary(self, input, turn);
        turn.observer_mut()
            .observe(TurnObservation::CircuitTurnCompleted {
                circuit: type_name::<Self>(),
                output: type_name::<Self::Out<'message>>(),
            });
        output
    }

    fn with_observer<Observer: TurnObserver>(
        self,
        observer: Observer,
    ) -> ObservedCircuit<Self, Observer> {
        ObservedCircuit::new(self, observer)
    }
}

pub struct ObservedCircuit<InstalledCircuit, Observer> {
    circuit: InstalledCircuit,
    observer: Observer,
}

impl<InstalledCircuit, Observer> ObservedCircuit<InstalledCircuit, Observer> {
    pub const fn new(circuit: InstalledCircuit, observer: Observer) -> Self {
        Self { circuit, observer }
    }

    pub const fn circuit(&self) -> &InstalledCircuit {
        &self.circuit
    }

    pub const fn observer(&self) -> &Observer {
        &self.observer
    }

    pub const fn observer_mut(&mut self) -> &mut Observer {
        &mut self.observer
    }

    pub fn into_parts(self) -> (InstalledCircuit, Observer) {
        (self.circuit, self.observer)
    }
}

impl<InstalledCircuit: Circuit, Observer: TurnObserver>
    ObservedCircuit<InstalledCircuit, Observer>
{
    #[must_use = "observed circuit turn outcomes must be delivered or settled"]
    #[inline(always)]
    pub fn turn<'message>(
        &mut self,
        input: InstalledCircuit::In<'message>,
    ) -> InstalledCircuit::Out<'message> {
        self.circuit
            .turn_in_context(input, &mut TurnContext::new(&mut self.observer))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{NoopTurnObserver, Switchboard, TurnContext};

    #[derive(Debug, Eq, PartialEq)]
    struct TestSwitchboard;

    impl StateMachine for TestSwitchboard {}

    impl Participant for TestSwitchboard {
        type In<'message> = u8;
        type Out<'message> = u16;
    }

    impl Switchboard for TestSwitchboard {
        type BoundaryIn<'message> = u8;
        type BoundaryOut<'message> = u16;

        fn route<'message>(&mut self, input: Self::In<'message>) -> Self::Out<'message> {
            u16::from(input)
        }
    }

    #[derive(Debug, Eq, PartialEq)]
    struct TestCircuit {
        switchboard: TestSwitchboard,
    }

    impl StateMachine for TestCircuit {}

    impl Participant for TestCircuit {
        type In<'message> = u8;
        type Out<'message> = u16;
    }

    impl CircuitInputOf<TestCircuit> for u8 {
        type Outcome = u16;

        fn turn<Observer: TurnObserver>(
            self,
            circuit: &mut TestCircuit,
            turn: &mut TurnContext<Observer>,
        ) -> Self::Outcome {
            turn.route(&mut circuit.switchboard, self)
        }
    }

    impl Circuit for TestCircuit {
        type Switchboard = TestSwitchboard;

        fn switchboard(&self) -> &Self::Switchboard {
            &self.switchboard
        }

        fn switchboard_mut(&mut self) -> &mut Self::Switchboard {
            &mut self.switchboard
        }
    }

    #[derive(Default)]
    struct CountingObserver(usize);

    impl TurnObserver for CountingObserver {
        fn observe(&mut self, _observation: TurnObservation) {
            self.0 = self.0.saturating_add(1);
        }
    }

    #[test]
    fn circuit_turns_use_their_declared_participant_boundary() {
        let mut circuit = TestCircuit {
            switchboard: TestSwitchboard,
        };

        assert_eq!(circuit.turn(7), 7);
        assert_eq!(circuit.switchboard(), &TestSwitchboard);
        assert_eq!(*circuit.switchboard_mut(), TestSwitchboard);
    }

    #[test]
    fn observed_circuits_reuse_one_observer_across_turns() {
        let mut program = TestCircuit {
            switchboard: TestSwitchboard,
        }
        .with_observer(CountingObserver::default());

        assert_eq!(program.turn(7), 7);
        assert_eq!(program.turn(8), 8);
        assert_eq!(
            program.circuit(),
            &TestCircuit {
                switchboard: TestSwitchboard,
            }
        );
        assert_eq!(program.observer().0, 8);
        assert_eq!(program.observer_mut().0, 8);
        let (_circuit, observer) = program.into_parts();
        assert_eq!(observer.0, 8);
    }

    #[test]
    fn turn_context_accessors_preserve_the_observer() {
        let mut turn = TurnContext::new(CountingObserver::default());

        assert_eq!(turn.observer().0, 0);
        assert_eq!(turn.observer_mut().0, 0);
        assert_eq!(turn.into_observer().0, 0);
        let _turn = TurnContext::new(NoopTurnObserver);
    }
}