1
use serde::Deserialize;
2

            
3
use super::TaskContext;
4

            
5
/// This struct represents a task dependency. A task can depend on other tasks.
6
/// If a task depends on another task, the dependent task must be executed before
7
/// the dependent task.
8
#[derive(Debug, Deserialize)]
9
pub struct TaskDependencyArgs {
10
  /// The name of the task to depend on
11
  pub name: String,
12
}
13

            
14
#[derive(Debug, Deserialize)]
15
#[serde(untagged)]
16
pub enum TaskDependency {
17
  String(String),
18
  TaskDependency(Box<TaskDependencyArgs>),
19
}
20

            
21
impl TaskDependency {
22
  pub fn run(&self, context: &TaskContext) -> anyhow::Result<()> {
23
    match self {
24
      TaskDependency::String(name) => self.execute(context, name),
25
      TaskDependency::TaskDependency(args) => args.execute(context),
26
    }
27
  }
28

            
29
  fn execute(&self, context: &TaskContext, task_name: &str) -> anyhow::Result<()> {
30
    assert!(!task_name.is_empty());
31

            
32
    let task = context
33
      .task_root
34
      .tasks
35
      .get(task_name)
36
      .ok_or_else(|| anyhow::anyhow!("Task not found"))?;
37

            
38
    log::trace!("Task: {:?}", task);
39

            
40
    {
41
      let mut stack = context
42
        .execution_stack
43
        .lock()
44
        .map_err(|e| anyhow::anyhow!("Failed to lock execution stack - {}", e))?;
45

            
46
      if stack.contains(task_name) {
47
        anyhow::bail!("Circular dependency detected - {}", task_name);
48
      }
49

            
50
      stack.insert(task_name.into());
51
    }
52

            
53
    let mut context = TaskContext::from_context(context);
54
    task.run(&mut context)?;
55

            
56
    Ok(())
57
  }
58
}
59

            
60
impl TaskDependencyArgs {
61
20
  pub fn execute(&self, context: &TaskContext) -> anyhow::Result<()> {
62
20
    assert!(!self.name.is_empty());
63

            
64
20
    let task_name: &str = &self.name;
65
20
    let task = context
66
20
      .task_root
67
20
      .tasks
68
20
      .get(task_name)
69
20
      .ok_or_else(|| anyhow::anyhow!("Task not found"))?;
70

            
71
20
    log::trace!("Task: {:?}", task);
72

            
73
    {
74
20
      let mut stack = context
75
20
        .execution_stack
76
20
        .lock()
77
20
        .map_err(|e| anyhow::anyhow!("Failed to lock execution stack - {}", e))?;
78

            
79
20
      if stack.contains(task_name) {
80
        anyhow::bail!("Circular dependency detected - {}", task_name);
81
20
      }
82
20

            
83
20
      stack.insert(task_name.into());
84
20
    }
85
20

            
86
20
    let mut context = TaskContext::from_context(context);
87
20
    task.run(&mut context)?;
88

            
89
20
    Ok(())
90
20
  }
91
}
92

            
93
#[cfg(test)]
94
mod test {
95
  use hashbrown::HashMap;
96
  use std::sync::Arc;
97

            
98
  use crate::schema::{
99
    Task,
100
    TaskRoot,
101
  };
102

            
103
  use super::*;
104

            
105
  #[test]
106
20
  fn test_task_dependency_1() -> anyhow::Result<()> {
107
20
    let yaml = "
108
20
      name: task1
109
20
    ";
110

            
111
20
    let task_dependency = serde_yaml::from_str::<TaskDependency>(yaml)?;
112
20
    if let TaskDependency::TaskDependency(args) = task_dependency {
113
20
      assert_eq!(args.name, "task1");
114
    } else {
115
      panic!("Expected TaskDependency::TaskDependency");
116
    }
117

            
118
20
    Ok(())
119
20
  }
120

            
121
  #[test]
122
20
  fn test_task_dependency_2() -> anyhow::Result<()> {
123
20
    let yaml = "\"task1\"";
124

            
125
20
    let task_dependency = serde_yaml::from_str::<TaskDependency>(yaml)?;
126
20
    if let TaskDependency::String(name) = task_dependency {
127
20
      assert_eq!(name, "task1");
128
    } else {
129
      panic!("Expected TaskDependency::TaskString");
130
    }
131

            
132
20
    Ok(())
133
20
  }
134

            
135
  #[test]
136
20
  fn test_task_dependency_3() -> anyhow::Result<()> {
137
20
    let yaml = "\"\"";
138

            
139
20
    let task_dependency = serde_yaml::from_str::<TaskDependency>(yaml)?;
140
20
    if let TaskDependency::String(name) = task_dependency {
141
20
      assert_eq!(name, "");
142
    } else {
143
      panic!("Expected TaskDependency::TaskString");
144
    }
145

            
146
20
    Ok(())
147
20
  }
148

            
149
  #[test]
150
20
  fn test_task_dependency_4() -> anyhow::Result<()> {
151
20
    let yaml = "
152
20
      name:
153
20
    ";
154

            
155
20
    let task_dependency = serde_yaml::from_str::<TaskDependencyArgs>(yaml)?;
156
20
    assert_eq!(task_dependency.name, "");
157

            
158
20
    Ok(())
159
20
  }
160

            
161
  #[test]
162
20
  fn test_task_dependency_5() -> anyhow::Result<()> {
163
20
    let yaml = "
164
20
      name: task_a
165
20
    ";
166
20

            
167
20
    let task_yaml = "
168
20
      commands:
169
20
        - command: echo 1
170
20
          verbose: false
171
20
    ";
172

            
173
20
    let task = serde_yaml::from_str::<Task>(task_yaml)?;
174
20
    let mut hm = HashMap::new();
175
20
    hm.insert("task_a".into(), task);
176
20

            
177
20
    let root = Arc::new(TaskRoot::from_hashmap(hm));
178
20
    let task_dependency = serde_yaml::from_str::<TaskDependencyArgs>(yaml)?;
179
20
    let result = task_dependency.execute(&TaskContext::empty_with_root(root));
180
20
    assert!(result.is_ok());
181

            
182
20
    Ok(())
183
20
  }
184
}