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
#![deny(missing_docs)]
//! `barley-runtime`
//!
//! This crate contains the runtime for the `barley` workflow engine. It
//! provides the [`Action`] trait, which is the main interface for defining
//! actions that can be executed by the engine. It also provides the
//! [`Context`] struct, which is used to pass information between actions.
//!
//! [`Action`]: trait.Action.html
//! [`Context`]: struct.Context.html
use async_trait::async_trait;
pub use anyhow::{Result, Error};
use std::sync::Arc;
use std::collections::{VecDeque, HashMap};
pub use uuid::Uuid as Id;
pub use barley_proc::barley_action;
/// A measurable, reversible task.
///
/// Any `Action` can test its environment to see if
/// it needs to run at all, and can undo any changes
/// it has made. Any `Action` can also depend on
/// other `Action`s, and the engine will ensure that
/// all dependencies are run before the `Action` itself.
#[async_trait]
pub trait Action: Send + Sync {
/// Check if the action needs to be run.
///
/// This method is called before the action is run,
/// and can be used to check if the action needs to
/// run at all. If this method returns `false`, the
/// action has not run yet, and the engine will
/// proceed to run it. If this method returns `true`,
/// the action has already run, and the engine will
/// skip it.
async fn check(&self, ctx: &mut Context) -> Result<bool>;
/// Check if the action's dependencies need to be run.
///
/// This method is called internally, and should not
/// be called directly. It is used to check if any
/// of the action's dependencies need to be run.
async fn check_deps(&self, ctx: &mut Context) -> Result<bool>;
/// Run the action.
async fn perform(&self, ctx: &mut Context) -> Result<()>;
/// Undo the action.
async fn rollback(&self, ctx: &mut Context) -> Result<()>;
/// Get the action's ID.
fn id(&self) -> Id;
/// Add a dependency to the action.
fn add_dep(&mut self, action: Arc<dyn Action>);
}
/// A context for running actions.
///
/// There should only be one of these per workflow
#[derive(Default)]
pub struct Context<'ctx> {
actions: VecDeque<Arc<dyn Action + 'ctx>>,
variables: HashMap<String, String>,
callbacks: ContextCallbacks
}
impl<'ctx> Context<'ctx> {
/// Create a new context with the given callbacks.
///
/// If you don't want any callbacks, it's recommended
/// to use the [`Default`] implementation instead.
///
/// [`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html
pub fn new(callbacks: ContextCallbacks) -> Self {
Self {
actions: VecDeque::new(),
variables: HashMap::new(),
callbacks
}
}
/// Add an action to the context.
///
/// This method adds an action to the context, and
/// returns a reference to the action. The action
/// will be run when the context is run.
///
/// You can use the returned reference as a
/// dependency for other actions.
pub fn add_action<A: Action + 'ctx>(&mut self, action: A) -> Arc<dyn Action + 'ctx> {
let action = Arc::new(action);
self.actions.push_back(action.clone());
action
}
/// Run the context.
///
/// While processing the actions, it will
/// call the callbacks if they are set.
pub async fn run(&mut self) -> Result<()> {
while let Some(action) = self.actions.pop_front() {
if !action.check(self).await? {
if let Some(callback) = self.callbacks.on_action_started {
callback(action.as_ref());
}
match action.perform(self).await {
Ok(_) => {
if let Some(callback) = self.callbacks.on_action_finished {
callback(action.as_ref());
}
},
Err(err) => {
if let Some(callback) = self.callbacks.on_action_failed {
callback(action.as_ref(), &err);
}
action.rollback(self).await?;
}
}
}
}
Ok(())
}
/// Sets a variable in the context.
///
/// This can be used to send information between
/// actions. For example, you could set a return code
/// in one action, and check it in another.
pub fn set_variable(&mut self, name: &str, value: &str) {
self.variables.insert(name.to_string(), value.to_string());
}
/// Gets a variable from the context.
///
/// If the variable doesn't exist, this method
/// returns `None`.
pub fn get_variable(&self, name: &str) -> Option<&str> {
self.variables.get(name).map(|s| s.as_str())
}
}
/// Callbacks for the context.
///
/// These callbacks are set by interfaces, and are
/// usually not set by scripts directly.
#[derive(Default)]
pub struct ContextCallbacks {
/// Called when an action is started.
pub on_action_started: Option<fn(&dyn Action)>,
/// Called when an action is completed successfully.
pub on_action_finished: Option<fn(&dyn Action)>,
/// Called when an action fails.
pub on_action_failed: Option<fn(&dyn Action, &anyhow::Error)>
}