Skip to main content

scheduler/
scheduler.rs

1use std::time::Duration;
2
3use chrono::prelude::*;
4
5use crate::error::SchedulerError;
6
7pub enum Scheduler {
8    /// Set to execute on set time periods
9    Cron(cron::Schedule),
10
11    /// Set to execute exactly `duration` away from the previous execution.
12    /// If
13    Interval {
14        interval_duration: Duration,
15        execute_at_startup: bool,
16    },
17
18    /// Multi shceduler: the execution is trigger where at least one of the schedulers in matched
19    Multi(Vec<Scheduler>),
20
21    /// Set to execute to never
22    Never,
23}
24
25impl Scheduler {
26    pub fn from(schedule: &[&dyn TryToScheduler]) -> Result<Scheduler, SchedulerError> {
27        schedule.to_scheduler()
28    }
29
30    // Determine the next time we should execute (from a reference point)
31    pub fn next(&mut self, after: &DateTime<Utc>) -> Option<DateTime<Utc>> {
32        match *self {
33            Scheduler::Cron(ref cs) => cs.after(after).next(),
34
35            Scheduler::Interval {
36                ref interval_duration,
37                ref mut execute_at_startup,
38            } => {
39                if *execute_at_startup {
40                    *execute_at_startup = false;
41                    Some(*after)
42                } else {
43                    let ch_duration = match chrono::Duration::from_std(*interval_duration) {
44                        Ok(value) => value,
45                        Err(_) => {
46                            return None;
47                        }
48                    };
49                    Some(*after + ch_duration)
50                }
51            }
52
53            Scheduler::Multi(ref mut schedulers) => {
54                let mut result = None;
55                for scheduler in schedulers {
56                    if let Some(local_next) = scheduler.next(after) {
57                        result = match result {
58                            Some(current_next) => {
59                                if local_next < current_next {
60                                    Some(local_next)
61                                } else {
62                                    Some(current_next)
63                                }
64                            }
65                            None => Some(local_next),
66                        }
67                    }
68                }
69                result
70            }
71
72            Scheduler::Never => None,
73        }
74    }
75}
76
77pub trait TryToScheduler {
78    fn to_scheduler(&self) -> Result<Scheduler, SchedulerError>;
79}
80
81impl TryToScheduler for Vec<String> {
82    fn to_scheduler(&self) -> Result<Scheduler, SchedulerError> {
83        let refs: Vec<&str> = self.iter().map(|s| s.as_ref()).collect();
84        refs.to_scheduler()
85    }
86}
87
88impl TryToScheduler for Vec<&str> {
89    fn to_scheduler(&self) -> Result<Scheduler, SchedulerError> {
90        match self.len() {
91            0 => Ok(Scheduler::Never),
92            1 => self[0].to_scheduler(),
93            _ => {
94                let mut result = vec![];
95                for scheduler in self {
96                    result.push(scheduler.to_scheduler()?);
97                }
98                Ok(Scheduler::Multi(result))
99            }
100        }
101    }
102}
103
104impl TryToScheduler for Vec<&dyn TryToScheduler> {
105    fn to_scheduler(&self) -> Result<Scheduler, SchedulerError> {
106        (&self[..]).to_scheduler()
107    }
108}
109
110impl TryToScheduler for &[&dyn TryToScheduler] {
111    fn to_scheduler(&self) -> Result<Scheduler, SchedulerError> {
112        match self.len() {
113            0 => Ok(Scheduler::Never),
114            1 => self[0].to_scheduler(),
115            _ => {
116                let mut result = vec![];
117                for scheduler in *self {
118                    result.push(scheduler.to_scheduler()?);
119                }
120                Ok(Scheduler::Multi(result))
121            }
122        }
123    }
124}
125
126impl<'a> TryToScheduler for &'a str {
127    fn to_scheduler(&self) -> Result<Scheduler, SchedulerError> {
128        Ok(Scheduler::Cron(self.parse().map_err(|err| {
129            SchedulerError::ScheduleDefinitionError {
130                message: format!("Cannot create schedule for [{self}]. Err: {err:?}"),
131            }
132        })?))
133    }
134}
135
136impl TryToScheduler for String {
137    fn to_scheduler(&self) -> Result<Scheduler, SchedulerError> {
138        self.as_str().to_scheduler()
139    }
140}
141
142impl TryToScheduler for Duration {
143    fn to_scheduler(&self) -> Result<Scheduler, SchedulerError> {
144        Ok(Scheduler::Interval {
145            interval_duration: *self,
146            execute_at_startup: false,
147        })
148    }
149}
150
151impl TryToScheduler for (Duration, bool) {
152    fn to_scheduler(&self) -> Result<Scheduler, SchedulerError> {
153        Ok(Scheduler::Interval {
154            interval_duration: self.0,
155            execute_at_startup: self.1,
156        })
157    }
158}
159
160impl From<Vec<Scheduler>> for Scheduler {
161    fn from(val: Vec<Scheduler>) -> Self {
162        Scheduler::Multi(val)
163    }
164}
165
166#[cfg(test)]
167pub mod test {
168
169    use super::*;
170
171    #[test]
172    fn never_should_not_schedule() {
173        let mut schedule = Scheduler::Never;
174        assert_eq!(None, schedule.next(&Utc::now()))
175    }
176
177    #[test]
178    fn interval_should_schedule_plus_duration() {
179        let now = Utc::now();
180        let secs = 10;
181        let mut schedule = Duration::new(secs, 0).to_scheduler().unwrap();
182
183        let next = schedule.next(&now).unwrap();
184
185        assert!(next.timestamp() >= now.timestamp() + (secs as i64));
186    }
187
188    #[test]
189    fn interval_should_schedule_at_startup() {
190        let now = Utc::now();
191        let secs = 10;
192        let mut schedule = (Duration::new(secs, 0), true).to_scheduler().unwrap();
193
194        let first = schedule.next(&now).unwrap();
195        assert_eq!(now.timestamp(), first.timestamp());
196
197        let next = schedule.next(&now).unwrap();
198        assert!(next.timestamp() >= now.timestamp() + (secs as i64));
199    }
200
201    #[test]
202    fn should_build_an_interval_schedule_from_duration() {
203        let schedule = Duration::new(1, 1).to_scheduler().unwrap();
204        match schedule {
205            Scheduler::Interval {
206                ..
207            } => (),
208            _ => panic!(),
209        }
210    }
211
212    #[test]
213    fn should_build_a_periodic_schedule_from_str() {
214        let schedule = "* * * * * *".to_scheduler().unwrap();
215        match schedule {
216            Scheduler::Cron(_) => (),
217            _ => panic!(),
218        }
219    }
220
221    #[test]
222    fn should_build_a_multi_scheduler_from_empty_array() {
223        let schedule = Scheduler::from(&[]).unwrap();
224        match schedule {
225            Scheduler::Never => (),
226            _ => panic!(),
227        }
228    }
229
230    #[test]
231    fn should_build_a_multi_scheduler_from_single_entry_array() {
232        let schedule = Scheduler::from(&[&vec!["* * * * * *"]]).unwrap();
233        match schedule {
234            Scheduler::Cron(_) => (),
235            _ => panic!(),
236        }
237    }
238
239    #[test]
240    fn should_build_a_multi_scheduler_from_array() {
241        let schedule = Scheduler::from(&[&"* * * * * *", &Duration::from_secs(9)]).unwrap();
242        match schedule {
243            Scheduler::Multi(inner) => {
244                match inner[0] {
245                    Scheduler::Cron(_) => (),
246                    _ => panic!(),
247                };
248                match inner[1] {
249                    Scheduler::Interval {
250                        ..
251                    } => (),
252                    _ => panic!(),
253                };
254            }
255            _ => panic!(),
256        }
257    }
258
259    #[test]
260    fn cron_should_be_time_zone_aware_with_utc() {
261        let mut schedule = "* 11 10 * * *".to_scheduler().unwrap();
262        let date = Utc.with_ymd_and_hms(2010, 1, 1, 10, 10, 0).unwrap();
263
264        let expected_utc = Utc.with_ymd_and_hms(2010, 1, 1, 10, 11, 0).unwrap();
265
266        let next = schedule.next(&date).unwrap();
267
268        assert_eq!(next, expected_utc);
269    }
270
271    // #[test]
272    // fn cron_should_be_time_zone_aware_with_custom_time_zone() {
273    //     let mut schedule = "* 11 10 * * *".to_scheduler().unwrap();
274
275    //     let date = Utc.with_ymd_and_hms(2010, 1, 1, 10, 10, 0).unwrap();
276    //     let expected_utc = Utc.with_ymd_and_hms(2010, 1, 2, 9, 11, 0).unwrap();
277
278    //     let next = schedule.next(&date).unwrap();
279
280    //     assert_eq!(next.with_timezone(&Utc), expected_utc);
281    // }
282
283    #[test]
284    fn multi_should_return_first_possible_next_execution() {
285        let mut schedule = Scheduler::from(&[&"* 10 10 * * *", &"* 20 20 * * *"]).unwrap();
286
287        {
288            let date = Utc.with_ymd_and_hms(2010, 1, 1, 10, 8, 0).unwrap();
289            let expected = Utc.with_ymd_and_hms(2010, 1, 1, 10, 10, 0).unwrap();
290
291            let next = schedule.next(&date).unwrap();
292            assert_eq!(next.with_timezone(&Utc), expected);
293        }
294
295        {
296            let date = Utc.with_ymd_and_hms(2010, 1, 1, 11, 8, 0).unwrap();
297            let expected = Utc.with_ymd_and_hms(2010, 1, 1, 20, 20, 0).unwrap();
298
299            let next = schedule.next(&date).unwrap();
300            assert_eq!(next.with_timezone(&Utc), expected);
301        }
302
303        {
304            let date = Utc.with_ymd_and_hms(2010, 1, 1, 22, 8, 0).unwrap();
305            let expected = Utc.with_ymd_and_hms(2010, 1, 2, 10, 10, 0).unwrap();
306
307            let next = schedule.next(&date).unwrap();
308            assert_eq!(next.with_timezone(&Utc), expected);
309        }
310    }
311}