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)>
}