pipecircuit 0.0.6

Correct. Robust. Fast. Portable.
Documentation
use core::fmt;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use = "turn observations must be recorded or explicitly discarded"]
pub enum TurnObservation {
    CircuitTurnBegan {
        circuit: &'static str,
        input: &'static str,
    },
    CircuitTurnCompleted {
        circuit: &'static str,
        output: &'static str,
    },
    PipeSendBegan {
        participant: &'static str,
        input: &'static str,
        transport: &'static str,
    },
    PipeSendCompleted {
        participant: &'static str,
        transport: &'static str,
        outcome: &'static str,
    },
    ParticipantStepBegan {
        participant: &'static str,
        input: &'static str,
    },
    ParticipantStepCompleted {
        participant: &'static str,
        outcome: &'static str,
    },
    SwitchboardRouteBegan {
        switchboard: &'static str,
        input: &'static str,
    },
    SwitchboardRouteCompleted {
        switchboard: &'static str,
        output: &'static str,
    },
}

pub trait TurnObserver {
    fn observe(&mut self, observation: TurnObservation);
}

impl<Observer: TurnObserver + ?Sized> TurnObserver for &mut Observer {
    #[inline(always)]
    fn observe(&mut self, observation: TurnObservation) {
        (**self).observe(observation);
    }
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct NoopTurnObserver;

impl TurnObserver for NoopTurnObserver {
    #[inline(always)]
    fn observe(&mut self, _observation: TurnObservation) {}
}

impl fmt::Display for TurnObservation {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::CircuitTurnBegan { circuit, input } => write!(
                formatter,
                "circuit-turn-began circuit={circuit} input={input}"
            ),
            Self::CircuitTurnCompleted { circuit, output } => write!(
                formatter,
                "circuit-turn-completed circuit={circuit} output={output}"
            ),
            Self::PipeSendBegan {
                participant,
                input,
                transport,
            } => write!(
                formatter,
                "pipe-send-began participant={participant} input={input} transport={transport}"
            ),
            Self::PipeSendCompleted {
                participant,
                transport,
                outcome,
            } => write!(
                formatter,
                "pipe-send-completed participant={participant} transport={transport} outcome={outcome}"
            ),
            Self::ParticipantStepBegan { participant, input } => write!(
                formatter,
                "participant-step-began participant={participant} input={input}"
            ),
            Self::ParticipantStepCompleted {
                participant,
                outcome,
            } => write!(
                formatter,
                "participant-step-completed participant={participant} outcome={outcome}"
            ),
            Self::SwitchboardRouteBegan { switchboard, input } => write!(
                formatter,
                "switchboard-route-began switchboard={switchboard} input={input}"
            ),
            Self::SwitchboardRouteCompleted {
                switchboard,
                output,
            } => write!(
                formatter,
                "switchboard-route-completed switchboard={switchboard} output={output}"
            ),
        }
    }
}

#[cfg(test)]
mod tests {
    extern crate alloc;

    use alloc::string::ToString as _;
    use core::mem::size_of;

    use super::*;
    use crate::TurnContext;

    #[test]
    fn every_observation_has_a_flat_display_form() {
        let observations = [
            TurnObservation::CircuitTurnBegan {
                circuit: "circuit",
                input: "input",
            },
            TurnObservation::CircuitTurnCompleted {
                circuit: "circuit",
                output: "output",
            },
            TurnObservation::PipeSendBegan {
                participant: "machine",
                input: "input",
                transport: "inline",
            },
            TurnObservation::PipeSendCompleted {
                participant: "machine",
                transport: "inline",
                outcome: "output",
            },
            TurnObservation::ParticipantStepBegan {
                participant: "machine",
                input: "input",
            },
            TurnObservation::ParticipantStepCompleted {
                participant: "machine",
                outcome: "output",
            },
            TurnObservation::SwitchboardRouteBegan {
                switchboard: "switchboard",
                input: "input",
            },
            TurnObservation::SwitchboardRouteCompleted {
                switchboard: "switchboard",
                output: "output",
            },
        ];
        let displayed = observations.map(|observation| observation.to_string());

        assert_eq!(
            [
                "circuit-turn-began circuit=circuit input=input",
                "circuit-turn-completed circuit=circuit output=output",
                "pipe-send-began participant=machine input=input transport=inline",
                "pipe-send-completed participant=machine transport=inline outcome=output",
                "participant-step-began participant=machine input=input",
                "participant-step-completed participant=machine outcome=output",
                "switchboard-route-began switchboard=switchboard input=input",
                "switchboard-route-completed switchboard=switchboard output=output",
            ],
            displayed
        );
    }

    #[test]
    fn borrowed_and_noop_observers_accept_observations() {
        let observation = TurnObservation::ParticipantStepCompleted {
            participant: "machine",
            outcome: "output",
        };
        let mut noop = NoopTurnObserver;
        let borrowed = &mut noop;

        borrowed.observe(observation);
    }

    #[test]
    fn noop_turn_observation_carries_no_runtime_state() {
        assert_eq!(size_of::<NoopTurnObserver>(), 0);
        assert_eq!(size_of::<TurnContext<NoopTurnObserver>>(), 0);
    }
}