1use std::time::Duration;
2
3use chrono::prelude::*;
4
5use crate::error::SchedulerError;
6
7pub enum Scheduler {
8 Cron(cron::Schedule),
10
11 Interval {
14 interval_duration: Duration,
15 execute_at_startup: bool,
16 },
17
18 Multi(Vec<Scheduler>),
20
21 Never,
23}
24
25impl Scheduler {
26 pub fn from(schedule: &[&dyn TryToScheduler]) -> Result<Scheduler, SchedulerError> {
27 schedule.to_scheduler()
28 }
29
30 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]
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}