1use crate::{
2 actions::Action,
3 checks::{Check, CheckError},
4 context::Context,
5 triggers::{Trigger, TriggerError},
6};
7use log::{debug, error, info};
8use std::{sync::mpsc, thread};
9use thiserror::Error;
10
11#[derive(Debug, Error)]
13pub enum StartError {
14 #[error("You have to define at least one trigger.")]
15 NoTriggers,
16 #[error("Trigger failed: {0}.")]
17 MisconfiguredTrigger(#[from] TriggerError),
18 #[error("Check failed: {0}.")]
19 FailedCheck(#[from] CheckError),
20}
21
22pub fn start(
24 triggers: Vec<Box<dyn Trigger>>,
25 check: &mut Box<dyn Check>,
26 actions: &mut [Box<dyn Action>],
27) -> Result<(), StartError> {
28 let (tx, rx) = mpsc::channel::<Option<Context>>();
29
30 if triggers.is_empty() {
31 return Err(StartError::NoTriggers);
32 }
33
34 for trigger in triggers {
35 let tx = tx.clone();
36 thread::spawn(move || {
37 let result = trigger.listen(tx);
38 if let Err(err) = result {
39 error!("Trigger failed: {err}.");
40 }
41 });
42 }
43
44 debug!("Waiting on triggers.");
45 while let Ok(Some(mut context)) = rx.recv() {
46 match check.check(&mut context) {
47 Ok(true) => {
48 info!(
49 "There are updates, {}.",
50 if actions.is_empty() {
51 "pulling"
52 } else {
53 "running actions"
54 }
55 );
56 for action in actions.iter_mut() {
57 let result = action.run(&context);
58 if let Err(err) = result {
59 error!("Action failed, we will not continue: {err}.");
60 break;
61 }
62 }
63 }
64 Ok(false) => {
65 debug!("There are no updates.");
66 }
67 Err(err) => {
68 error!("Check failed: {err}.");
69 }
70 }
71 }
72
73 debug!("Finished running.");
74
75 Ok(())
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use crate::{
82 actions::{Action, MockAction},
83 checks::{Check, MockCheck},
84 triggers::{MockTrigger, Trigger},
85 };
86 use std::collections::HashMap;
87
88 #[test]
89 fn it_should_call_once() {
90 let mut mock_trigger = MockTrigger::new();
92 mock_trigger.expect_listen().returning(|tx| {
93 tx.send(Some(HashMap::new()))?;
94 tx.send(None)?;
95 Ok(())
96 });
97 let triggers: Vec<Box<dyn Trigger>> = vec![Box::new(mock_trigger)];
98
99 let mut mock_check = MockCheck::new();
101 mock_check.expect_check().times(1).returning(|_| Ok(true));
102 let mut check: Box<dyn Check> = Box::new(mock_check);
103
104 let mut mock_action = MockAction::new();
106 mock_action.expect_run().times(1).returning(|_| Ok(()));
107 let actions: &mut [Box<dyn Action>] = &mut [Box::new(mock_action)];
108
109 let result = start(triggers, &mut check, actions);
110 assert!(result.is_ok());
111 }
112
113 #[test]
114 fn it_should_not_run_on_a_false_check() {
115 let mut mock_trigger = MockTrigger::new();
117 mock_trigger.expect_listen().returning(|tx| {
118 tx.send(Some(HashMap::new()))?;
119 tx.send(None)?;
120 Ok(())
121 });
122 let triggers: Vec<Box<dyn Trigger>> = vec![Box::new(mock_trigger)];
123
124 let mut mock_check = MockCheck::new();
126 mock_check.expect_check().times(1).returning(|_| Ok(false));
127 let mut check: Box<dyn Check> = Box::new(mock_check);
128
129 let mut mock_action = MockAction::new();
131 mock_action.expect_run().times(0);
132 let actions: &mut [Box<dyn Action>] = &mut [Box::new(mock_action)];
133
134 let result = start(triggers, &mut check, actions);
135 assert!(result.is_ok());
136 }
137
138 #[test]
139 fn it_should_not_run_on_a_failed_check() {
140 let mut mock_trigger = MockTrigger::new();
142 mock_trigger.expect_listen().returning(|tx| {
143 tx.send(Some(HashMap::new()))?;
144 tx.send(None)?;
145 Ok(())
146 });
147 let triggers: Vec<Box<dyn Trigger>> = vec![Box::new(mock_trigger)];
148
149 let mut mock_check = MockCheck::new();
151 mock_check
152 .expect_check()
153 .times(1)
154 .returning(|_| Err(CheckError::Misconfigured(String::from("Testing purposes."))));
155 let mut check: Box<dyn Check> = Box::new(mock_check);
156
157 let mut mock_action = MockAction::new();
159 mock_action.expect_run().times(0);
160 let actions: &mut [Box<dyn Action>] = &mut [Box::new(mock_action)];
161
162 let result = start(triggers, &mut check, actions);
163 assert!(result.is_ok());
164 }
165
166 #[test]
167 fn it_should_fail_without_triggers() {
168 let triggers: Vec<Box<dyn Trigger>> = vec![];
170
171 let mut mock_check = MockCheck::new();
173 mock_check.expect_check().times(0);
174 let mut check: Box<dyn Check> = Box::new(mock_check);
175
176 let mut mock_action = MockAction::new();
178 mock_action.expect_run().times(0);
179 let actions: &mut [Box<dyn Action>] = &mut [Box::new(mock_action)];
180
181 let result = start(triggers, &mut check, actions);
182 assert!(result.is_err());
183 }
184}