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
use std::fmt::Debug;
use async_trait::async_trait;
pub use crate::error::Error;
pub use crate::state::State;
pub use crate::task::{Task, Transition};
mod error;
mod state;
mod task;
/// Trait for automatons
///
/// Automatons execute a series of tasks. This trait defines the behavior that automatons must
/// implement so that they can be executed inside a runtime.
#[async_trait]
pub trait Automaton: Debug {
/// Returns the initial state for the execution.
///
/// Automatons can customize the state that is passed to the tasks. This can be useful to inject
/// secrets or configuration on which tasks might depend. By default, an empty state is used.
fn initial_state(&self) -> State {
State::new()
}
/// Returns the first task in the automaton.
///
/// The initial task defines the entry point of the automaton. This task will be executed with
/// the initial state.
fn initial_task(&self) -> Box<dyn Task>;
/// Returns the task that is called after the completed transition.
///
/// When a tasks returns `Transition::Complete` to end the execution of the automaton, a final
/// task is executed. This can be useful to perform any teardown actions, for example to remove
/// resources that were created in a previous step. By default, a "noop" task is executed that
/// performs no action.
fn complete_task(&self) -> Box<dyn Task> {
Box::new(NoopTask)
}
/// Executes the automaton.
///
/// Automatons execute a series of tasks. When started, the automaton first initializes a new
/// state. Then, it iterates over the list of tasks. It initializes and executes each task one
/// by one until it either reaches the end of the list or a task returns `Transition::Complete`.
/// In both instances, the task returned by the `complete_task` method is executed and the
/// automaton shuts down.
#[cfg_attr(feature = "tracing", tracing::instrument)]
async fn execute(&self) -> Result<State, Error> {
let mut state = self.initial_state();
let mut task = self.initial_task();
loop {
task = match task.execute(&mut state).await? {
Transition::Next(task) => task,
Transition::Complete => break,
}
}
let mut complete_task = self.complete_task();
complete_task.execute(&mut state).await?;
Ok(state)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
struct NoopTask;
#[async_trait]
impl Task for NoopTask {
#[cfg_attr(feature = "tracing", tracing::instrument)]
async fn execute(&mut self, _state: &mut State) -> Result<Transition, Error> {
Ok(Transition::Complete)
}
}
#[cfg(test)]
mod tests {
use super::NoopTask;
#[test]
fn trait_send() {
fn assert_send<T: Send>() {}
assert_send::<NoopTask>();
}
#[test]
fn trait_sync() {
fn assert_sync<T: Sync>() {}
assert_sync::<NoopTask>();
}
}