pipecircuit 0.0.23

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

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use = "circuit reaction outcomes must be handled"]
pub enum CircuitReactionOutcome<Completion> {
    ContinueReaction,
    AwaitingReaction,
    Completed { completion: Completion },
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use = "circuit send outcomes must be handled"]
pub enum CircuitSendOutcome<From, Completion> {
    Immediate { from: From },
    Submitted,
    Completed { completion: Completion },
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use = "circuit flow outcomes must be handled"]
pub enum CircuitFlowOutcome<Completion> {
    Submitted,
    Completed { completion: Completion },
}

#[inline]
pub fn flow<'message, Board, Completion, Failure, const CAPACITY: usize>(
    switchboard: &mut Board,
    from: Board::From<'message>,
    pending: &mut crate::PendingRoutes<Board::To<'message>, CAPACITY>,
    mut send: impl FnMut(
        Board::To<'message>,
    ) -> Result<CircuitSendOutcome<Board::From<'message>, Completion>, Failure>,
) -> Result<CircuitFlowOutcome<Completion>, crate::FlowFailure<Failure, Board::To<'message>>>
where
    Board: Switchboard,
{
    let mut route = switchboard.route(from);
    loop {
        let next = pending
            .admit(route)
            .map_err(|rejected| crate::FlowFailure::Capacity {
                rejected,
                maximum_pending: CAPACITY,
            })?;
        let destination = match next.or_else(|| pending.pop()) {
            Some(destination) => destination,
            None => return Ok(CircuitFlowOutcome::Submitted),
        };
        match send(destination).map_err(crate::FlowFailure::Dispatch)? {
            CircuitSendOutcome::Immediate { from } => route = switchboard.route(from),
            CircuitSendOutcome::Submitted => route = crate::Route::none(),
            CircuitSendOutcome::Completed { completion } => {
                return Ok(CircuitFlowOutcome::Completed { completion });
            }
        }
    }
}

pub trait Circuit<Board: Switchboard> {
    type Reactor: Reactor;
    type Completion;
    type Failure;

    fn reactor(&mut self) -> &mut Self::Reactor;

    fn react(
        &mut self,
        switchboard: &mut Board,
        reaction: &<Self::Reactor as Reactor>::Reaction,
    ) -> Result<CircuitReactionOutcome<Self::Completion>, Self::Failure>;
}

#[cfg(test)]
mod tests {
    use super::*;
    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))
        }
    }

    struct TestReactor;

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

        fn wait_for_reaction(&mut self) -> Result<Self::Reaction, Self::Failure> {
            Ok(7)
        }
    }

    struct InstalledCircuit {
        reactor: TestReactor,
    }

    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> {
            Ok(CircuitReactionOutcome::Completed {
                completion: switchboard.route(*reaction).into_single()?,
            })
        }
    }

    #[test]
    fn a_circuit_reacts_through_only_its_selected_switchboard() {
        let mut circuit = InstalledCircuit {
            reactor: TestReactor,
        };
        let mut switchboard = Router;
        assert_eq!(
            circuit.react(&mut switchboard, &7),
            Ok(CircuitReactionOutcome::Completed { completion: 7 })
        );
    }

    #[test]
    fn flow_routes_every_immediate_output_before_submission() {
        let mut switchboard = Router;
        let mut sent = 0;

        assert_eq!(
            flow(
                &mut switchboard,
                1,
                &mut crate::PendingRoutes::<_, 0>::new(),
                |to| {
                    sent += 1;
                    if to < 3 {
                        let Ok(next) = u8::try_from(to + 1) else {
                            return Ok(CircuitSendOutcome::Completed { completion: () });
                        };
                        Ok::<CircuitSendOutcome<u8, ()>, core::convert::Infallible>(
                            CircuitSendOutcome::Immediate { from: next },
                        )
                    } else {
                        Ok(CircuitSendOutcome::Submitted)
                    }
                }
            ),
            Ok(CircuitFlowOutcome::Submitted)
        );
        assert_eq!(sent, 3);
    }
}