1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
//! Run your CLI programs as state machines with persistence and recovery abilities. When such a
//! program breaks you'll have opportunity to change the external world (create a missing folder,
//! change a file permissions or something) and continue the program from the step it was
//! interrupted on.
//!
//! # Usage
//!
//! Let's toss two coins and make sure they both landed on the same side. We express the behaviour
//! as two states of our machine. Step logic is implemented in `State::next()` methods which
//! return the next state or `None` for the last step (the full code is in `examples/coin.rs`).
//! ```rust
//! #[derive(Debug, Serialize, Deserialize, From)]
//! enum Machine {
//!     FirstToss(FirstToss),
//!     SecondToss(SecondToss),
//! }
//!
//! #[derive(Debug, Serialize, Deserialize)]
//! struct FirstToss;
//! impl State<Machine> for FirstToss {
//!     type Error = anyhow::Error;
//!
//!     fn next(self) -> StepResult {
//!         let coin = Coin::toss();
//!         println!("First coin: {:?}", coin);
//!         Ok(Some(SecondToss { first_coin: coin }.into()))
//!     }
//! }
//!
//! #[derive(Debug, Serialize, Deserialize)]
//! struct SecondToss {
//!     first_coin: Coin,
//! }
//! impl State<Machine> for SecondToss {
//!     type Error = anyhow::Error;
//!
//!     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)
//!     }
//! }
//! ```
//!
//! Then we start our machine like this:
//! ```rust
//! let init_state = Machine::FirstToss(FirstToss);
//! let mut engine = Engine::<Machine>::new(init_state)?.restore()?;
//! engine.drop_error();
//! engine.run()?;
//! ```
//! We initialize the `Engine` with the first step. Then we restore the previous state if the
//! process was interrupted (e.g. by an error). Then we drop a possible error and run all the steps
//! to completion.
//!
//! Let's run it now:
//! ```sh
//! $ cargo run --example coin
//! First coin: Heads
//! Second coin: Tails
//! Error: Coins landed differently
//! ```
//!
//! We weren't lucky this time and the program resulted in an error. Let's run it again:
//! ```sh
//! $ cargo run --example coin
//! Second coin: Heads
//! Coins match
//! ```
//!
//! Notice that, thanks to the `restore()`, our machine run from the step it was interrupted,
//! knowing about the first coin landed on heads.
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::fmt;
use std::io;
use std::path::PathBuf;
use store::Store;

mod store;

type StdResult<T, E> = std::result::Result<T, E>;

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("IO error: {0}")]
    IO(#[from] io::Error),
    #[error("Serde error: {0}")]
    Serde(#[from] serde_json::Error),
    #[error("{0}")]
    Step(String),
}

pub type Result<T> = StdResult<T, Error>;

/// Represents state of a state machine M
pub trait State<M: State<M>> {
    type Error: fmt::Debug;

    /// Runs the current step and returns the next machine state or `None` if everything is done
    fn next(self) -> StdResult<Option<M>, Self::Error>;
}

/// Machine state with metadata to store
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Step<M> {
    /// Current state of the machine
    pub state: M,
    /// An error if any
    pub error: Option<String>,
}

impl<M> Step<M> {
    fn new(state: M) -> Self {
        Self { state, error: None }
    }
}

#[derive(Debug)]
pub struct Engine<M> {
    store: Store,
    step: Step<M>,
}

impl<M> Engine<M>
where
    M: fmt::Debug + Serialize + DeserializeOwned + State<M>,
{
    /// Creates an Engine using initial state
    pub fn new(state: M) -> Result<Self> {
        let store: Store = Store::new()?;
        let step = Step::new(state);
        Ok(Self { store, step })
    }

    /// Use another store path
    pub fn with_store_path(mut self, path: impl Into<PathBuf>) -> Self {
        let path = path.into();
        self.store.path = path;
        self
    }

    /// Restores an Engine from the previous run
    pub fn restore(mut self) -> Result<Self> {
        if let Some(step) = self.store.load()? {
            self.step = step;
        }
        Ok(self)
    }

    /// Runs all steps to completion
    pub fn run(mut self) -> Result<()> {
        if let Some(e) = self.step.error.as_ref() {
            return Err(crate::Error::Step(format!(
                "Previous run resulted in an error: {} on step: {:?}",
                e, self.step.state
            )));
        }

        loop {
            log::info!("Running step: {:?}", &self.step.state);
            let state_backup = serde_json::to_string(&self.step.state)?;
            match self.step.state.next() {
                Ok(state) => {
                    if let Some(state) = state {
                        self.step = Step::new(state); // TODO
                        self.save()?;
                    } else {
                        log::info!("Finished successfully");
                        self.store.clean()?;
                        break;
                    }
                }
                Err(e) => {
                    self.step.state = serde_json::from_str(&state_backup)?;
                    let err_str = format!("{:?}", e);
                    self.step.error = Some(err_str.clone());
                    self.save()?;
                    return Err(crate::Error::Step(err_str));
                }
            }
        }
        Ok(())
    }

    /// Drops the previous error
    pub fn drop_error(&mut self) {
        self.step.error = None;
    }

    fn save(&self) -> Result<()> {
        self.store.save(&self.step)?;
        Ok(())
    }
}