gw_bin/
start.rs

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/// A custom error implementation for the start function
12#[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
22/// The main program loop, that runs the triggers, checks and actions infinitely.
23pub 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        // Setup mock triggers.
91        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        // Setup mock check.
100        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        // Setup mock action.
105        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        // Setup mock triggers.
116        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        // Setup mock check.
125        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        // Setup mock action.
130        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        // Setup mock triggers.
141        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        // Setup mock check.
150        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        // Setup mock action.
158        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        // Setup empty triggers.
169        let triggers: Vec<Box<dyn Trigger>> = vec![];
170
171        // Setup mock check.
172        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        // Setup mock action.
177        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}