1use super::*;
2
3#[derive(Debug)]
5pub struct CronExpression {
6 fields: [CronField; Self::FIELD_COUNT],
7}
8
9impl Display for CronExpression {
10 fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
11 write!(formatter, "{} {} {} {} {}",
12 &self.fields[0],
13 &self.fields[1],
14 &self.fields[2],
15 &self.fields[3],
16 &self.fields[4])?;
17 Ok(())
18 }
19}
20
21impl FromStr for CronExpression {
22 type Err = CronParsingError;
23
24 fn from_str(string: &str) -> Result<Self, Self::Err> {
25 CronExpression::parse::<CurdsCronParser>(string)
26 }
27}
28
29impl CronExpression {
30 const FIELD_COUNT : usize = 5;
31 const FIELD_DELIMITER : char = ' ';
32
33 fn parse<TFieldParser>(expression: &str) -> Result<CronExpression, CronParsingError>
34 where TFieldParser : CronFieldParser {
35 let parts: Vec<&str> = expression
36 .split(Self::FIELD_DELIMITER)
37 .filter(|part| part.len() > 0)
38 .collect();
39 if parts.len() != Self::FIELD_COUNT {
40 return Err(CronParsingError::FieldCount {
41 expression: expression.to_owned(),
42 parts: parts.len()
43 });
44 }
45 Ok(CronExpression {
46 fields : [
47 TFieldParser::parse(CronDatePart::Minutes, parts[0])?,
48 TFieldParser::parse(CronDatePart::Hours, parts[1])?,
49 TFieldParser::parse(CronDatePart::DayOfMonth, parts[2])?,
50 TFieldParser::parse(CronDatePart::Month, parts[3])?,
51 TFieldParser::parse(CronDatePart::DayOfWeek, parts[4])?,
52 ]
53 })
54 }
55
56 pub fn is_match<Tz>(&self, datetime: &DateTime<Tz>) -> bool
66 where Tz: TimeZone {
67 for field in &self.fields {
68 if !field.is_match(datetime) {
69 return false;
70 }
71 }
72 true
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn displays_fields() {
82 let test_object = CronExpression {
83 fields: [
84 CronField::new(CronDatePart::Hours, vec![CronValue::Any]),
85 CronField::new(CronDatePart::Hours, vec![CronValue::Any]),
86 CronField::new(CronDatePart::Hours, vec![CronValue::Any]),
87 CronField::new(CronDatePart::Hours, vec![CronValue::Any]),
88 CronField::new(CronDatePart::Hours, vec![CronValue::Any]),
89 ],
90 };
91
92 assert_eq!("* * * * *", &format!("{}", test_object));
93 }
94
95 #[test]
96 fn too_long_expression_fails() {
97 CronExpression::parse::<CurdsCronParser>("* * * * * *")
98 .expect_err("Expected a long expression to fail");
99 }
100
101 #[test]
102 fn too_short_expression_fails() {
103 CronExpression::parse::<CurdsCronParser>("* * * *")
104 .expect_err("Expected a short expression to fail");
105 }
106
107 macro_rules! expect_parsing {
108 ($context:expr, $sequence:expr => ($($expected_part:expr, $expected_value:expr),+)) => {
109 $(
110 $context
111 .expect()
112 .with(predicate::eq($expected_part), predicate::eq($expected_value))
113 .times(1)
114 .in_sequence(&mut $sequence)
115 .returning(|_, _| {
116 Ok(CronField::new($expected_part, Vec::<CronValue>::new()))
117 });
118 )+
119 };
120 }
121
122 #[test]
123 fn parses_correctly_with_parser() -> Result<(), CronParsingError> {
124 let mut sequence = Sequence::new();
125 let context = MockCronFieldParser::parse_context();
126 expect_parsing! { context, sequence =>
127 (
128 CronDatePart::Minutes, "Minutes",
129 CronDatePart::Hours, "Hours",
130 CronDatePart::DayOfMonth, "DayOfMonth",
131 CronDatePart::Month, "Month",
132 CronDatePart::DayOfWeek, "DayOfWeek"
133 )
134 }
135
136 CronExpression::parse::<MockCronFieldParser>("Minutes Hours DayOfMonth Month DayOfWeek")?;
137 Ok(())
138 }
139
140 fn false_field() -> CronField {
141 CronField::new(CronDatePart::Minutes, vec![CronValue::Single(99)])
142 }
143 fn true_field() -> CronField {
144 CronField::new(CronDatePart::Minutes, vec![CronValue::Any])
145 }
146
147 #[test]
148 fn any_nonmatch_field_returns_false() {
149 let expression = CronExpression {
150 fields: [true_field(), false_field(), true_field(), true_field(), true_field()]
151 };
152
153 assert_eq!(false, expression.is_match(&Utc::now()));
154 }
155
156 #[test]
157 fn all_fields_match_returns_true() {
158 let expression = CronExpression {
159 fields: [true_field(), true_field(), true_field(), true_field(), true_field()]
160 };
161
162 assert_eq!(true, expression.is_match(&Utc::now()));
163 }
164}