ready-active-safe 0.1.3

Lifecycle engine for externally driven systems
Documentation
//! Runner with policy enforcement.
//!
//! Demonstrates the `runtime::Runner` event loop with a forward-only
//! policy. Shows what happens when a transition is allowed vs denied.

#![allow(clippy::print_stdout, clippy::enum_glob_use)]

use ready_active_safe::prelude::*;

#[derive(Debug, Clone, PartialEq, Eq)]
enum Mode {
    Ready,
    Active,
    Safe,
}

impl core::fmt::Display for Mode {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "{self:?}")
    }
}

#[derive(Debug)]
enum Event {
    Start,
    Reset,
    Fault,
}

#[derive(Debug, Clone, PartialEq, Eq)]
enum Command {
    Init,
    Shutdown,
}

struct System;

impl Machine for System {
    type Mode = Mode;
    type Event = Event;
    type Command = Command;

    fn initial_mode(&self) -> Mode {
        Mode::Ready
    }

    fn on_event(&self, mode: &Mode, event: &Event) -> Decision<Mode, Command> {
        use Command::*;
        use Event::*;
        use Mode::*;

        match (mode, event) {
            (Ready, Start) => transition(Active).emit(Init),
            (Active, Reset) => transition(Ready),
            (Active, Fault) => transition(Safe).emit(Shutdown),
            _ => ignore(),
        }
    }
}

/// Only allows forward progression: Ready → Active → Safe.
struct ForwardOnly;

impl Policy<Mode> for ForwardOnly {
    fn is_allowed(&self, from: &Mode, to: &Mode) -> bool {
        matches!(
            (from, to),
            (Mode::Ready, Mode::Active) | (Mode::Active, Mode::Safe)
        )
    }
}

fn main() {
    let system = System;
    let policy = ForwardOnly;
    let mut runner = Runner::new(&system);

    println!("Starting in {:?}", runner.mode());

    // Allowed: Ready → Active
    let commands = runner.feed_checked(&Event::Start, &policy);
    println!("\nEvent: Start");
    println!("  mode: {:?}, commands: {:?}", runner.mode(), commands);

    // Denied: Active → Ready (policy blocks backward transitions)
    let result = runner.feed_checked(&Event::Reset, &policy);
    println!("\nEvent: Reset");
    match &result {
        Ok(cmds) => println!("  mode: {:?}, commands: {cmds:?}", runner.mode()),
        Err(err) => println!("  denied: {err}"),
    }
    println!("  mode unchanged: {:?}", runner.mode());

    // Allowed: Active → Safe
    let commands = runner.feed_checked(&Event::Fault, &policy);
    println!("\nEvent: Fault");
    println!("  mode: {:?}, commands: {:?}", runner.mode(), commands);
}