step-machine 0.2.0

Run your CLI program as state machines with persistence and recovery abilities
Documentation
use anyhow::{ensure, Result};
use derive_more::From;
use rand::Rng;
use serde::{Deserialize, Serialize};

use step_machine::{Engine, State};

type StepResult = Result<Option<Machine>>;

fn main() -> Result<()> {
    env_logger::init();
    let init_state = FirstToss.into();
    let mut engine = Engine::<Machine>::new(init_state)?.restore()?;
    engine.drop_error()?;
    engine.run()?;
    Ok(())
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
enum Coin {
    Heads,
    Tails,
}

impl Coin {
    fn toss() -> Self {
        if rand::thread_rng().gen::<bool>() {
            Coin::Heads
        } else {
            Coin::Tails
        }
    }
}

#[derive(Debug, Serialize, Deserialize, From)]
enum Machine {
    FirstToss(FirstToss),
    SecondToss(SecondToss),
}

impl State<Machine> for Machine {
    type Error = anyhow::Error;

    fn next(self) -> StepResult {
        match self {
            Machine::FirstToss(state) => state.next(),
            Machine::SecondToss(state) => state.next(),
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
struct FirstToss;
impl FirstToss {
    fn next(self) -> StepResult {
        let first_coin = Coin::toss();
        println!("First coin: {:?}", first_coin);
        Ok(Some(SecondToss { first_coin }.into()))
    }
}

#[derive(Debug, Serialize, Deserialize)]
struct SecondToss {
    first_coin: Coin,
}
impl SecondToss {
    fn next(self) -> StepResult {
        let second_coin = Coin::toss();
        println!("Second coin: {:?}", second_coin);
        ensure!(second_coin == self.first_coin, "Coins landed differently");
        println!("Coins match");
        Ok(None)
    }
}