barley_runtime/
lib.rs

1#![deny(missing_docs)]
2
3//! `barley-runtime`
4//! 
5//! This crate contains the runtime for the `barley` workflow engine. It
6//! provides the [`Action`] trait, which is the main interface for defining
7//! actions that can be executed by the engine. It also provides the
8//! [`Runtime`] struct, which is used to run workflows.
9//! 
10//! [`Action`]: trait.Action.html
11//! [`Runtime`]: struct.Runtime.html
12
13use uuid::Uuid;
14use std::sync::Arc;
15use thiserror::Error;
16use async_trait::async_trait;
17
18/// The prelude for the `barley-runtime` crate.
19/// 
20/// This module contains all of the important types
21/// and traits for the `barley-runtime` crate. It
22/// should be used instead of importing the types
23/// directly.
24pub mod prelude;
25
26mod context;
27mod runtime;
28
29pub use runtime::{Runtime, RuntimeBuilder};
30
31/// A measurable, reversible task.
32/// 
33/// Any `Action` can test its environment to see if
34/// it needs to run at all, and can undo any changes
35/// it has made. Any `Action` can also depend on
36/// other `Action`s, and the engine will ensure that
37/// all dependencies are run before the `Action` itself.
38#[async_trait]
39pub trait Action: Send + Sync {
40  /// Run the action.
41  /// 
42  /// This method takes a [`Runtime`] object, which
43  /// contains the context for the action. It also
44  /// takes an [`Operation`], which is used to
45  /// determine what the action should do.
46  async fn run(&self, runtime: Runtime, operation: Operation) -> Result<Option<ActionOutput>, ActionError>;
47
48  /// Probe the action for specific information.
49  async fn probe(&self, runtime: Runtime) -> Result<Probe, ActionError>;
50
51  /// Load required state.
52  async fn load_state(&self, _builder: &mut RuntimeBuilder) {}
53
54  /// Get the display name of the action.
55  fn display_name(&self) -> String;
56}
57
58/// A usable action object.
59/// 
60/// This struct is used by actions to store their
61/// dependencies and identification. It should
62/// not be constructed directly, unless you are
63/// writing a custom Action.
64#[derive(Clone)]
65pub struct ActionObject {
66  action: Arc<dyn Action>,
67  deps: Vec<ActionObject>,
68  id: Id
69}
70
71impl ActionObject {
72  /// Create a new action object.
73  /// 
74  /// This method should not be called directly,
75  /// unless you are writing a custom Action.
76  pub fn new(action: Arc<dyn Action>) -> Self {
77    Self {
78      action,
79      deps: Vec::new(),
80      id: Id::default()
81    }
82  }
83
84  /// Get the display name of the action.
85  pub fn display_name(&self) -> String {
86    self.action.display_name()
87  }
88
89  pub(crate) fn id(&self) -> Id {
90    self.id
91  }
92
93  pub(crate) fn deps(&self) -> Vec<ActionObject> {
94    self.deps.clone()
95  }
96
97  pub(crate) async fn probe(&self, ctx: Runtime) -> Result<Probe, ActionError> {
98    self.action.probe(ctx).await
99  }
100
101  pub(crate) async fn run(&self, ctx: Runtime, operation: Operation) -> Result<Option<ActionOutput>, ActionError> {
102    self.action.run(ctx, operation).await
103  }
104
105  /// Add a dependency to the action.
106  pub fn requires(&mut self, action: ActionObject) {
107    self.deps.push(action);
108  }
109
110  /// Load the state
111  pub async fn load_state(&self, builder: &mut RuntimeBuilder) {
112    self.action.load_state(builder).await;
113  }
114}
115
116impl<A> From<A> for ActionObject
117where
118  A: Action + 'static
119{
120  fn from(action: A) -> Self {
121    Self::new(Arc::new(action))
122  }
123}
124
125/// Callbacks for the context.
126/// 
127/// These callbacks are set by interfaces, and are
128/// usually not set by scripts directly.
129#[derive(Default, Clone)]
130pub struct ContextCallbacks {
131  /// Called when an action is started.
132  pub on_action_started: Option<fn(ActionObject)>,
133  /// Called when an action is completed successfully.
134  pub on_action_finished: Option<fn(ActionObject)>,
135  /// Called when an action fails.
136  pub on_action_failed: Option<fn(ActionObject, &ActionError)>
137}
138
139/// A unique identifier for an action.
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
141pub struct Id(Uuid);
142
143impl Default for Id {
144  fn default() -> Self {
145    Self(Uuid::new_v4())
146  }
147}
148
149impl std::fmt::Display for Id {
150  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151    self.0.fmt(f)
152  }
153}
154
155/// The output of an action.
156/// 
157/// When an [`Action`] is run, it can return a value
158/// back to the context. This value can be used by
159/// other actions depending on said value.
160/// 
161/// [`Action`]: trait.Action.html
162#[derive(Debug, Clone)]
163pub enum ActionOutput {
164  /// A string.
165  String(String),
166  /// An integer (i64).
167  Integer(i64),
168  /// A floating-point number (f64).
169  Float(f64),
170  /// A boolean.
171  Boolean(bool)
172}
173
174impl TryFrom<ActionOutput> for String {
175  type Error = ActionError;
176
177  fn try_from(value: ActionOutput) -> Result<Self, Self::Error> {
178    match value {
179      ActionOutput::String(value) => Ok(value),
180      _ => Err(ActionError::OutputConversionFailed("String".to_string()))
181    }
182  }
183}
184
185impl TryFrom<ActionOutput> for i64 {
186  type Error = ActionError;
187
188  fn try_from(value: ActionOutput) -> Result<Self, Self::Error> {
189    match value {
190      ActionOutput::Integer(value) => Ok(value),
191      _ => Err(ActionError::OutputConversionFailed("i64".to_string()))
192    }
193  }
194}
195
196impl TryFrom<ActionOutput> for f64 {
197  type Error = ActionError;
198
199  fn try_from(value: ActionOutput) -> Result<Self, Self::Error> {
200    match value {
201      ActionOutput::Float(value) => Ok(value),
202      _ => Err(ActionError::OutputConversionFailed("f64".to_string()))
203    }
204  }
205}
206
207impl TryFrom<ActionOutput> for bool {
208  type Error = ActionError;
209
210  fn try_from(value: ActionOutput) -> Result<Self, Self::Error> {
211    match value {
212      ActionOutput::Boolean(value) => Ok(value),
213      _ => Err(ActionError::OutputConversionFailed("bool".to_string()))
214    }
215  }
216}
217
218impl From<String> for ActionOutput {
219  fn from(value: String) -> Self {
220    Self::String(value)
221  }
222}
223
224impl From<i64> for ActionOutput {
225  fn from(value: i64) -> Self {
226    Self::Integer(value)
227  }
228}
229
230impl From<f64> for ActionOutput {
231  fn from(value: f64) -> Self {
232    Self::Float(value)
233  }
234}
235
236impl From<bool> for ActionOutput {
237  fn from(value: bool) -> Self {
238    Self::Boolean(value)
239  }
240}
241
242impl From<&str> for ActionOutput {
243  fn from(value: &str) -> Self {
244    Self::String(value.to_string())
245  }
246}
247
248/// An input for an action.
249/// 
250/// Action inputs are not required to use this
251/// enum, but it is recommended to do so. It allows
252/// users to pass both static values and dependency
253/// outputs to actions.
254pub enum ActionInput<T> {
255  /// A static value.
256  Static(T),
257  /// A value from an action.
258  Dynamic(ActionObject)
259}
260
261impl<T> ActionInput<T> {
262  /// Creates a new input from an action.
263  pub fn new_dynamic(value: ActionObject) -> Self {
264    Self::Dynamic(value)
265  }
266
267  /// Creates a new input from a static value.
268  pub fn new_static(value: T) -> Self {
269    Self::Static(value)
270  }
271
272  /// Returns the static value, or `None` if the input
273  /// is an action.
274  pub fn static_value(&self) -> Option<&T> {
275    match self {
276      Self::Static(value) => Some(value),
277      _ => None
278    }
279  }
280
281  /// Returns the action, or `None` if the input is
282  /// static.
283  pub fn dynamic(&self) -> Option<ActionObject> {
284    match self {
285      Self::Dynamic(action) => Some(action.clone()),
286      _ => None
287    }
288  }
289
290  /// Returns `true` if the input is static.
291  pub fn is_static(&self) -> bool {
292    self.static_value().is_some()
293  }
294
295  /// Returns `true` if the input is an action.
296  pub fn is_dynamic(&self) -> bool {
297    self.dynamic().is_some()
298  }
299
300  /// Returns the static value, or panics if the input
301  /// is an action.
302  pub fn unwrap_static(&self) -> &T {
303    self.static_value().unwrap()
304  }
305
306  /// Returns the action, or panics if the input is
307  /// static.
308  pub fn unwrap_dynamic(&self) -> ActionObject {
309    self.dynamic().unwrap()
310  }
311}
312
313impl<T> From<T> for ActionInput<T> {
314  fn from(value: T) -> Self {
315    Self::new_static(value)
316  }
317}
318
319impl<T: Default> Default for ActionInput<T> {
320  fn default() -> Self {
321    Self::new_static(T::default())
322  }
323}
324
325/// Any error that can occur during an action.
326#[derive(Debug, Error, Clone)]
327#[non_exhaustive]
328pub enum ActionError {
329  /// An error occured internally in the action.
330  #[error("{0}")]
331  ActionFailed(String, String),
332  /// Action output conversion failed.
333  #[error("Could not convert ActionOutput to {0}")]
334  OutputConversionFailed(String),
335  /// An internal error occured, and should be reported.
336  #[error("An internal error occured, please report this error code: {0}")]
337  InternalError(&'static str),
338  /// An action which should have returned a value did not.
339  #[error("Dependency did not return a value")]
340  NoActionReturn,
341  /// The operation is not supported by the action.
342  #[error("Operation not supported")]
343  OperationNotSupported,
344  /// Required state was not loaded.
345  #[error("Required state was not loaded")]
346  StateNotLoaded
347}
348
349/// The operation to perform.
350/// 
351/// This enum is used to determine what an action
352/// should do. It is used by the [`run`] method.
353/// 
354/// [`run`]: trait.Action.html#method.run
355pub enum Operation {
356  /// Perform the action.
357  Perform,
358  /// Rollback the action.
359  Rollback
360}
361
362/// A probe for an action.
363/// 
364/// This struct is returned by the [`probe`] method
365/// of an [`Action`]. It contains information about
366/// the action, such as whether it needs to be run
367/// or not.
368/// 
369/// [`probe`]: trait.Action.html#method.probe
370/// [`Action`]: trait.Action.html
371#[derive(Debug, Clone)]
372pub struct Probe {
373  /// Whether the action needs to be run.
374  pub needs_run: bool,
375  /// Whether `rollback` is available.
376  pub can_rollback: bool
377}
378
379impl Default for Probe {
380  fn default() -> Self {
381    Self {
382      needs_run: true,
383      can_rollback: false
384    }
385  }
386}