pipecircuit 0.0.23

Correct. Robust. Fast. Portable. Pick four.
Documentation
use crate::{Circuit, CircuitReactionOutcome, Reactor, Switchboard};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ConductionFailure<ReactorFailure, CircuitFailure> {
    Reactor { failure: ReactorFailure },
    Circuit { failure: CircuitFailure },
}

pub type ConductionResult<Completion, ReactorFailure, CircuitFailure> =
    Result<Completion, ConductionFailure<ReactorFailure, CircuitFailure>>;

pub struct Pipecircuit<Board, InstalledCircuit>
where
    Board: Switchboard,
    InstalledCircuit: Circuit<Board>,
{
    switchboard: Board,
    circuit: InstalledCircuit,
}

impl<Board, InstalledCircuit> Pipecircuit<Board, InstalledCircuit>
where
    Board: Switchboard,
    InstalledCircuit: Circuit<Board>,
{
    pub const fn new(switchboard: Board, circuit: InstalledCircuit) -> Self {
        Self {
            switchboard,
            circuit,
        }
    }
}

impl<Board, InstalledCircuit> Pipecircuit<Board, InstalledCircuit>
where
    Board: Switchboard,
    InstalledCircuit: Circuit<Board>,
{
    pub const fn switchboard(&self) -> &Board {
        &self.switchboard
    }

    pub const fn switchboard_mut(&mut self) -> &mut Board {
        &mut self.switchboard
    }

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

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

    pub fn into_parts(self) -> (Board, InstalledCircuit) {
        (self.switchboard, self.circuit)
    }

    pub fn conduct(
        &mut self,
    ) -> ConductionResult<
        InstalledCircuit::Completion,
        <InstalledCircuit::Reactor as Reactor>::Failure,
        InstalledCircuit::Failure,
    > {
        loop {
            let reaction = self
                .circuit
                .reactor()
                .wait_for_reaction()
                .map_err(|failure| ConductionFailure::Reactor { failure })?;

            loop {
                match self
                    .circuit
                    .react(&mut self.switchboard, &reaction)
                    .map_err(|failure| ConductionFailure::Circuit { failure })?
                {
                    CircuitReactionOutcome::ContinueReaction => {}
                    CircuitReactionOutcome::AwaitingReaction => break,
                    CircuitReactionOutcome::Completed { completion } => return Ok(completion),
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[derive(Debug, Eq, PartialEq)]
    struct Router;

    impl Switchboard for Router {
        type From<'message> = u8;
        type To<'message> = u16;

        fn route<'message>(
            &mut self,
            input: Self::From<'message>,
        ) -> pipecircuit::Route<Self::To<'message>> {
            pipecircuit::Route::one(u16::from(input))
        }
    }

    #[derive(Debug, Eq, PartialEq)]
    struct TestReactor {
        next_input: u8,
        waits: u8,
    }

    impl Reactor for TestReactor {
        type Reaction = u8;
        type Failure = core::convert::Infallible;

        fn wait_for_reaction(&mut self) -> Result<Self::Reaction, Self::Failure> {
            self.waits = self.waits.saturating_add(1);
            Ok(self.next_input)
        }
    }

    #[derive(Debug, Eq, PartialEq)]
    struct InstalledCircuit {
        reactor: TestReactor,
        reactions: u8,
    }

    impl Circuit<Router> for InstalledCircuit {
        type Reactor = TestReactor;
        type Completion = u16;
        type Failure = crate::Route<u16>;

        fn reactor(&mut self) -> &mut Self::Reactor {
            &mut self.reactor
        }

        fn react(
            &mut self,
            switchboard: &mut Router,
            reaction: &u8,
        ) -> Result<CircuitReactionOutcome<Self::Completion>, Self::Failure> {
            self.reactions = self.reactions.saturating_add(1);
            if self.reactions == 1 {
                return Ok(CircuitReactionOutcome::ContinueReaction);
            }
            Ok(CircuitReactionOutcome::Completed {
                completion: switchboard.route(*reaction).into_single()?,
            })
        }
    }

    #[test]
    fn a_pipecircuit_owns_one_matching_switchboard_and_circuit() {
        let mut program = Pipecircuit::new(
            Router,
            InstalledCircuit {
                reactor: TestReactor {
                    next_input: 7,
                    waits: 0,
                },
                reactions: 0,
            },
        );

        assert_eq!(program.conduct(), Ok(7));
        assert_eq!(program.switchboard(), &Router);
        assert_eq!(*program.switchboard_mut(), Router);
        assert_eq!(
            program.circuit(),
            &InstalledCircuit {
                reactor: TestReactor {
                    next_input: 7,
                    waits: 1,
                },
                reactions: 2,
            }
        );
        assert_eq!(
            program.circuit_mut(),
            &InstalledCircuit {
                reactor: TestReactor {
                    next_input: 7,
                    waits: 1,
                },
                reactions: 2,
            }
        );
        let (switchboard, circuit) = program.into_parts();
        assert_eq!(switchboard, Router);
        assert_eq!(
            circuit,
            InstalledCircuit {
                reactor: TestReactor {
                    next_input: 7,
                    waits: 1,
                },
                reactions: 2,
            }
        );
    }
}