cron_expr/
time_unit.rs

1use std::collections::HashSet;
2use std::fmt;
3use std::fmt::Formatter;
4
5use crate::error::TimeUnitError;
6use crate::error::TimeUnitErrorKind::{InvalidRange, InvalidRangeEnd, InvalidRangeStart,
7    InvalidStep, InvalidValue, OutOfBounds};
8
9#[derive(Debug)]
10pub enum TimeUnitKind {
11    Minute,
12    Hour,
13    MonthDay,
14    Month,
15    WeekDay
16}
17
18impl fmt::Display for TimeUnitKind {
19    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
20        use TimeUnitKind::*;
21
22        f.write_str(match self {
23            Minute => "minute",
24            Hour => "hour",
25            MonthDay => "month day",
26            Month => "month",
27            WeekDay => "week day"
28        })
29    }
30}
31
32#[derive(Debug)]
33pub enum TimeUnitValue {
34    Wildcard,
35    ValidValues(HashSet<u32>)
36}
37
38impl TimeUnitValue {
39    pub fn includes(&self, value: u32) -> bool {
40        use TimeUnitValue::*;
41
42        match self {
43            Wildcard => true,
44            ValidValues(values) => values.contains(&value)
45        }
46    }
47
48    pub fn is_wildcard(&self) -> bool {
49        if let TimeUnitValue::Wildcard = self {
50            true
51        } else {
52            false
53        }
54    }
55}
56
57pub trait TimeUnit {
58    fn kind() -> TimeUnitKind;
59
60    fn new(value: TimeUnitValue) -> Self where Self: Sized;
61
62    fn parse_value(input: &str) -> Result<u32, TimeUnitError> {
63        let value: u32 = input.parse().map_err(|_| InvalidValue.err(input))?;
64        if value < Self::min_value() || value > Self::max_value() {
65            return Err(OutOfBounds.err(input));
66        }
67        Ok(value)
68    }
69
70    fn parse(input: &str) -> Result<Self, TimeUnitError> where Self: Sized {
71        if input == "*" {
72            return Ok(Self::new(TimeUnitValue::Wildcard))
73        }
74
75        let values: HashSet<u32> = input.split(",")
76            .map(|item| match item {
77                _ if item.contains("-") => {
78                    let split: Vec<&str> = item.split("-").collect();
79                    if split.len() != 2 {
80                        return Err(InvalidRange.err(item))
81                    }
82
83                    let start = Self::parse_value(split[0]).map_err(|err|
84                        InvalidRangeStart(Box::new(err)).err(item))?;
85                    let end = Self::parse_value(split[1]).map_err(|err|
86                        InvalidRangeEnd(Box::new(err)).err(item))?;
87                    Ok((start..=end).collect())
88                }
89                _ if item.starts_with("*/") => {
90                    if item.len() < 3 {
91                        return Err(InvalidStep.err(item));
92                    }
93
94                    let step = Self::parse_value(&item[2..])
95                        .map_err(|_| InvalidStep.err(item))?;
96                    Ok((Self::min_value()..=Self::max_value()).step_by(step as usize).collect())
97                }
98                _ => Ok(vec![Self::parse_value(item)?])
99            })
100            .collect::<Result<Vec<Vec<u32>>, TimeUnitError>>()?
101            .into_iter()
102            .flatten()
103            .collect();
104
105        Ok(Self::new(TimeUnitValue::ValidValues(values)))
106    }
107
108    fn min_value() -> u32;
109
110    fn max_value() -> u32;
111
112    fn value(&self) -> &TimeUnitValue;
113}
114
115macro_rules! define_time_unit {
116    ($name:ident, $min:expr, $max:expr) => {
117        #[derive(Debug)]
118        pub struct $name {
119            value: TimeUnitValue
120        }
121
122        impl TimeUnit for $name {
123            fn kind() -> TimeUnitKind {
124                TimeUnitKind::$name
125            }
126
127            fn new(value: TimeUnitValue) -> Self where Self: Sized {
128                $name {
129                    value
130                }
131            }
132
133            fn min_value() -> u32 {
134                $min
135            }
136
137            fn max_value() -> u32 {
138                $max
139            }
140
141            fn value(&self) -> &TimeUnitValue {
142                &self.value
143            }
144        }
145    }
146}
147
148define_time_unit!(Minute, 0, 59);
149define_time_unit!(Hour, 0, 23);
150define_time_unit!(MonthDay, 1, 31);
151define_time_unit!(Month, 1, 12);
152define_time_unit!(WeekDay, 1, 7);