Skip to main content

cron/
parsing.rs

1use std::{
2    convert::TryFrom,
3    str::{self, FromStr},
4};
5
6use nom::{
7    IResult,
8    branch::alt,
9    bytes::complete::tag,
10    character::complete::{alpha1, digit1, multispace0},
11    combinator::{all_consuming, eof, map, map_res, opt},
12    multi::separated_list1,
13    sequence::{delimited, separated_pair, terminated, tuple},
14};
15
16use crate::{
17    error::{Error, ErrorKind},
18    ordinal::*,
19    schedule::{Schedule, ScheduleFields},
20    specifier::*,
21    time_unit::*,
22};
23
24impl FromStr for Schedule {
25    type Err = Error;
26    fn from_str(expression: &str) -> Result<Self, Self::Err> {
27        match schedule(expression) {
28            Ok((_, schedule_fields)) => Ok(Schedule::new(String::from(expression), schedule_fields)), // Extract from nom tuple
29            Err(_) => Err(ErrorKind::Expression("Invalid cron expression.".to_owned()).into()),       //TODO: Details
30        }
31    }
32}
33impl TryFrom<&str> for Schedule {
34    type Error = Error;
35
36    fn try_from(value: &str) -> Result<Self, Self::Error> {
37        Self::from_str(value)
38    }
39}
40
41#[derive(Debug, PartialEq)]
42pub struct Field {
43    pub specifiers: Vec<RootSpecifier>, // TODO: expose iterator?
44}
45
46trait FromField
47where
48    Self: Sized,
49{
50    //TODO: Replace with std::convert::TryFrom when stable
51    fn from_field(field: Field) -> Result<Self, Error>;
52}
53
54impl<T> FromField for T
55where
56    T: TimeUnitField,
57{
58    fn from_field(field: Field) -> Result<T, Error> {
59        if field.specifiers.len() == 1 && field.specifiers.get(0).unwrap() == &RootSpecifier::from(Specifier::All) {
60            return Ok(T::all());
61        }
62        let mut ordinals = OrdinalSet::new();
63        for specifier in field.specifiers {
64            let specifier_ordinals: OrdinalSet = T::ordinals_from_root_specifier(&specifier)?;
65            for ordinal in specifier_ordinals {
66                ordinals.insert(T::validate_ordinal(ordinal)?);
67            }
68        }
69        Ok(T::from_ordinal_set(ordinals))
70    }
71}
72
73fn ordinal(i: &str) -> IResult<&str, u32> {
74    map_res(delimited(multispace0, digit1, multispace0), u32::from_str)(i)
75}
76
77fn name(i: &str) -> IResult<&str, String> {
78    map(delimited(multispace0, alpha1, multispace0), ToOwned::to_owned)(i)
79}
80
81fn point(i: &str) -> IResult<&str, Specifier> {
82    let (i, o) = ordinal(i)?;
83    Ok((i, Specifier::Point(o)))
84}
85
86fn named_point(i: &str) -> IResult<&str, RootSpecifier> {
87    let (i, n) = name(i)?;
88    Ok((i, RootSpecifier::NamedPoint(n)))
89}
90
91fn period(i: &str) -> IResult<&str, RootSpecifier> {
92    map(separated_pair(specifier, tag("/"), ordinal), |(start, step)| {
93        RootSpecifier::Period(start, step)
94    })(i)
95}
96
97fn period_with_any(i: &str) -> IResult<&str, RootSpecifier> {
98    map(separated_pair(specifier_with_any, tag("/"), ordinal), |(start, step)| {
99        RootSpecifier::Period(start, step)
100    })(i)
101}
102
103fn range(i: &str) -> IResult<&str, Specifier> {
104    map(separated_pair(ordinal, tag("-"), ordinal), |(start, end)| {
105        Specifier::Range(start, end)
106    })(i)
107}
108
109fn named_range(i: &str) -> IResult<&str, Specifier> {
110    map(separated_pair(name, tag("-"), name), |(start, end)| {
111        Specifier::NamedRange(start, end)
112    })(i)
113}
114
115fn all(i: &str) -> IResult<&str, Specifier> {
116    let (i, _) = tag("*")(i)?;
117    Ok((i, Specifier::All))
118}
119
120fn any(i: &str) -> IResult<&str, Specifier> {
121    let (i, _) = tag("?")(i)?;
122    Ok((i, Specifier::All))
123}
124
125fn specifier(i: &str) -> IResult<&str, Specifier> {
126    alt((all, range, point, named_range))(i)
127}
128
129fn specifier_with_any(i: &str) -> IResult<&str, Specifier> {
130    alt((any, specifier))(i)
131}
132
133fn root_specifier(i: &str) -> IResult<&str, RootSpecifier> {
134    alt((period, map(specifier, RootSpecifier::from), named_point))(i)
135}
136
137fn root_specifier_with_any(i: &str) -> IResult<&str, RootSpecifier> {
138    alt((period_with_any, map(specifier_with_any, RootSpecifier::from), named_point))(i)
139}
140
141fn root_specifier_list(i: &str) -> IResult<&str, Vec<RootSpecifier>> {
142    let list = separated_list1(tag(","), root_specifier);
143    let single_item = map(root_specifier, |spec| vec![spec]);
144    delimited(multispace0, alt((list, single_item)), multispace0)(i)
145}
146
147fn root_specifier_list_with_any(i: &str) -> IResult<&str, Vec<RootSpecifier>> {
148    let list = separated_list1(tag(","), root_specifier_with_any);
149    let single_item = map(root_specifier_with_any, |spec| vec![spec]);
150    delimited(multispace0, alt((list, single_item)), multispace0)(i)
151}
152
153fn field(i: &str) -> IResult<&str, Field> {
154    let (i, specifiers) = root_specifier_list(i)?;
155    Ok((
156        i,
157        Field {
158            specifiers,
159        },
160    ))
161}
162
163fn field_with_any(i: &str) -> IResult<&str, Field> {
164    let (i, specifiers) = root_specifier_list_with_any(i)?;
165    Ok((
166        i,
167        Field {
168            specifiers,
169        },
170    ))
171}
172
173fn shorthand_yearly(i: &str) -> IResult<&str, ScheduleFields> {
174    let (i, _) = tag("@yearly")(i)?;
175    let fields = ScheduleFields::new(
176        Seconds::from_ordinal(0),
177        Minutes::from_ordinal(0),
178        Hours::from_ordinal(0),
179        DaysOfMonth::from_ordinal(1),
180        Months::from_ordinal(1),
181        DaysOfWeek::all(),
182        Years::all(),
183    );
184    Ok((i, fields))
185}
186
187fn shorthand_monthly(i: &str) -> IResult<&str, ScheduleFields> {
188    let (i, _) = tag("@monthly")(i)?;
189    let fields = ScheduleFields::new(
190        Seconds::from_ordinal(0),
191        Minutes::from_ordinal(0),
192        Hours::from_ordinal(0),
193        DaysOfMonth::from_ordinal(1),
194        Months::all(),
195        DaysOfWeek::all(),
196        Years::all(),
197    );
198    Ok((i, fields))
199}
200
201fn shorthand_weekly(i: &str) -> IResult<&str, ScheduleFields> {
202    let (i, _) = tag("@weekly")(i)?;
203    let fields = ScheduleFields::new(
204        Seconds::from_ordinal(0),
205        Minutes::from_ordinal(0),
206        Hours::from_ordinal(0),
207        DaysOfMonth::all(),
208        Months::all(),
209        DaysOfWeek::from_ordinal(1),
210        Years::all(),
211    );
212    Ok((i, fields))
213}
214
215fn shorthand_daily(i: &str) -> IResult<&str, ScheduleFields> {
216    let (i, _) = tag("@daily")(i)?;
217    let fields = ScheduleFields::new(
218        Seconds::from_ordinal(0),
219        Minutes::from_ordinal(0),
220        Hours::from_ordinal(0),
221        DaysOfMonth::all(),
222        Months::all(),
223        DaysOfWeek::all(),
224        Years::all(),
225    );
226    Ok((i, fields))
227}
228
229fn shorthand_hourly(i: &str) -> IResult<&str, ScheduleFields> {
230    let (i, _) = tag("@hourly")(i)?;
231    let fields = ScheduleFields::new(
232        Seconds::from_ordinal(0),
233        Minutes::from_ordinal(0),
234        Hours::all(),
235        DaysOfMonth::all(),
236        Months::all(),
237        DaysOfWeek::all(),
238        Years::all(),
239    );
240    Ok((i, fields))
241}
242
243fn shorthand(i: &str) -> IResult<&str, ScheduleFields> {
244    let keywords = alt((
245        shorthand_yearly,
246        shorthand_monthly,
247        shorthand_weekly,
248        shorthand_daily,
249        shorthand_hourly,
250    ));
251    delimited(multispace0, keywords, multispace0)(i)
252}
253
254fn longhand(i: &str) -> IResult<&str, ScheduleFields> {
255    let seconds = map_res(field, Seconds::from_field);
256    let minutes = map_res(field, Minutes::from_field);
257    let hours = map_res(field, Hours::from_field);
258    let days_of_month = map_res(field_with_any, DaysOfMonth::from_field);
259    let months = map_res(field, Months::from_field);
260    let days_of_week = map_res(field_with_any, DaysOfWeek::from_field);
261    let years = opt(map_res(field, Years::from_field));
262    let fields = tuple((seconds, minutes, hours, days_of_month, months, days_of_week, years));
263
264    map(
265        terminated(fields, eof),
266        |(seconds, minutes, hours, days_of_month, months, days_of_week, years)| {
267            let years = years.unwrap_or_else(Years::all);
268            ScheduleFields::new(seconds, minutes, hours, days_of_month, months, days_of_week, years)
269        },
270    )(i)
271}
272
273fn schedule(i: &str) -> IResult<&str, ScheduleFields> {
274    all_consuming(alt((shorthand, longhand)))(i)
275}
276
277#[cfg(test)]
278mod test {
279    use super::*;
280
281    #[test]
282    fn test_nom_valid_number() {
283        let expression = "1997";
284        point(expression).unwrap();
285    }
286
287    #[test]
288    fn test_nom_invalid_point() {
289        let expression = "a";
290        assert!(point(expression).is_err());
291    }
292
293    #[test]
294    fn test_nom_valid_named_point() {
295        let expression = "WED";
296        named_point(expression).unwrap();
297    }
298
299    #[test]
300    fn test_nom_invalid_named_point() {
301        let expression = "8";
302        assert!(named_point(expression).is_err());
303    }
304
305    #[test]
306    fn test_nom_valid_period() {
307        let expression = "1/2";
308        period(expression).unwrap();
309    }
310
311    #[test]
312    fn test_nom_invalid_period() {
313        let expression = "Wed/4";
314        assert!(period(expression).is_err());
315    }
316
317    #[test]
318    fn test_nom_valid_number_list() {
319        let expression = "1,2";
320        field(expression).unwrap();
321        field_with_any(expression).unwrap();
322    }
323
324    #[test]
325    fn test_nom_invalid_number_list() {
326        let expression = ",1,2";
327        assert!(field(expression).is_err());
328        assert!(field_with_any(expression).is_err());
329    }
330
331    #[test]
332    fn test_nom_field_with_any_valid_any() {
333        let expression = "?";
334        field_with_any(expression).unwrap();
335    }
336
337    #[test]
338    fn test_nom_field_invalid_any() {
339        let expression = "?";
340        assert!(field(expression).is_err());
341    }
342
343    #[test]
344    fn test_nom_valid_range_field() {
345        let expression = "1-4";
346        range(expression).unwrap();
347    }
348
349    #[test]
350    fn test_nom_valid_period_all() {
351        let expression = "*/2";
352        period(expression).unwrap();
353    }
354
355    #[test]
356    fn test_nom_valid_period_range() {
357        let expression = "10-20/2";
358        period(expression).unwrap();
359    }
360
361    #[test]
362    fn test_nom_valid_period_named_range() {
363        let expression = "Mon-Thurs/2";
364        period(expression).unwrap();
365
366        let expression = "February-November/2";
367        period(expression).unwrap();
368    }
369
370    #[test]
371    fn test_nom_valid_period_point() {
372        let expression = "10/2";
373        period(expression).unwrap();
374    }
375
376    #[test]
377    fn test_nom_invalid_period_any() {
378        let expression = "?/2";
379        assert!(period(expression).is_err());
380    }
381
382    #[test]
383    fn test_nom_invalid_period_named_point() {
384        let expression = "Tues/2";
385        assert!(period(expression).is_err());
386
387        let expression = "February/2";
388        assert!(period(expression).is_err());
389    }
390
391    #[test]
392    fn test_nom_invalid_period_specifier_range() {
393        let expression = "10-12/*";
394        assert!(period(expression).is_err());
395    }
396
397    #[test]
398    fn test_nom_valid_period_with_any_all() {
399        let expression = "*/2";
400        period_with_any(expression).unwrap();
401    }
402
403    #[test]
404    fn test_nom_valid_period_with_any_range() {
405        let expression = "10-20/2";
406        period_with_any(expression).unwrap();
407    }
408
409    #[test]
410    fn test_nom_valid_period_with_any_named_range() {
411        let expression = "Mon-Thurs/2";
412        period_with_any(expression).unwrap();
413
414        let expression = "February-November/2";
415        period_with_any(expression).unwrap();
416    }
417
418    #[test]
419    fn test_nom_valid_period_with_any_point() {
420        let expression = "10/2";
421        period_with_any(expression).unwrap();
422    }
423
424    #[test]
425    fn test_nom_valid_period_with_any_any() {
426        let expression = "?/2";
427        period_with_any(expression).unwrap();
428    }
429
430    #[test]
431    fn test_nom_invalid_period_with_any_named_point() {
432        let expression = "Tues/2";
433        assert!(period_with_any(expression).is_err());
434
435        let expression = "February/2";
436        assert!(period_with_any(expression).is_err());
437    }
438
439    #[test]
440    fn test_nom_invalid_period_with_any_specifier_range() {
441        let expression = "10-12/*";
442        assert!(period_with_any(expression).is_err());
443    }
444
445    #[test]
446    fn test_nom_invalid_range_field() {
447        let expression = "-4";
448        assert!(range(expression).is_err());
449    }
450
451    #[test]
452    fn test_nom_valid_named_range_field() {
453        let expression = "TUES-THURS";
454        named_range(expression).unwrap();
455    }
456
457    #[test]
458    fn test_nom_invalid_named_range_field() {
459        let expression = "3-THURS";
460        assert!(named_range(expression).is_err());
461    }
462
463    #[test]
464    fn test_nom_valid_schedule() {
465        let expression = "* * * * * *";
466        schedule(expression).unwrap();
467    }
468
469    #[test]
470    fn test_nom_invalid_schedule() {
471        let expression = "* * * *";
472        assert!(schedule(expression).is_err());
473    }
474
475    #[test]
476    fn test_nom_valid_seconds_list() {
477        let expression = "0,20,40 * * * * *";
478        schedule(expression).unwrap();
479    }
480
481    #[test]
482    fn test_nom_valid_seconds_range() {
483        let expression = "0-40 * * * * *";
484        schedule(expression).unwrap();
485    }
486
487    #[test]
488    fn test_nom_valid_seconds_mix() {
489        let expression = "0-5,58 * * * * *";
490        schedule(expression).unwrap();
491    }
492
493    #[test]
494    fn test_nom_invalid_seconds_range() {
495        let expression = "0-65 * * * * *";
496        assert!(schedule(expression).is_err());
497    }
498
499    #[test]
500    fn test_nom_invalid_seconds_list() {
501        let expression = "103,12 * * * * *";
502        assert!(schedule(expression).is_err());
503    }
504
505    #[test]
506    fn test_nom_invalid_seconds_mix() {
507        let expression = "0-5,102 * * * * *";
508        assert!(schedule(expression).is_err());
509    }
510
511    #[test]
512    fn test_nom_valid_days_of_week_list() {
513        let expression = "* * * * * MON,WED,FRI";
514        schedule(expression).unwrap();
515    }
516
517    #[test]
518    fn test_nom_invalid_days_of_week_list() {
519        let expression = "* * * * * MON,TURTLE";
520        assert!(schedule(expression).is_err());
521    }
522
523    #[test]
524    fn test_nom_valid_days_of_week_range() {
525        let expression = "* * * * * MON-FRI";
526        schedule(expression).unwrap();
527    }
528
529    #[test]
530    fn test_nom_invalid_days_of_week_range() {
531        let expression = "* * * * * BEAR-OWL";
532        assert!(schedule(expression).is_err());
533    }
534
535    #[test]
536    fn test_nom_invalid_period_with_range_specifier() {
537        let expression = "10-12/10-12 * * * * ?";
538        assert!(schedule(expression).is_err());
539    }
540
541    #[test]
542    fn test_nom_valid_days_of_month_any() {
543        let expression = "* * * ? * *";
544        schedule(expression).unwrap();
545    }
546
547    #[test]
548    fn test_nom_valid_days_of_week_any() {
549        let expression = "* * * * * ?";
550        schedule(expression).unwrap();
551    }
552
553    #[test]
554    fn test_nom_valid_days_of_month_any_days_of_week_specific() {
555        let expression = "* * * ? * Mon,Thu";
556        schedule(expression).unwrap();
557    }
558
559    #[test]
560    fn test_nom_valid_days_of_week_any_days_of_month_specific() {
561        let expression = "* * * 1,2 * ?";
562        schedule(expression).unwrap();
563    }
564
565    #[test]
566    fn test_nom_valid_dom_and_dow_any() {
567        let expression = "* * * ? * ?";
568        schedule(expression).unwrap();
569    }
570
571    #[test]
572    fn test_nom_invalid_other_fields_any() {
573        let expression = "? * * * * *";
574        assert!(schedule(expression).is_err());
575
576        let expression = "* ? * * * *";
577        assert!(schedule(expression).is_err());
578
579        let expression = "* * ? * * *";
580        assert!(schedule(expression).is_err());
581
582        let expression = "* * * * ? *";
583        assert!(schedule(expression).is_err());
584    }
585
586    #[test]
587    fn test_nom_invalid_trailing_characters() {
588        let expression = "* * * * * *foo *";
589        assert!(schedule(expression).is_err());
590
591        let expression = "* * * * * * * foo";
592        assert!(schedule(expression).is_err());
593    }
594
595    /// Issue #86
596    #[test]
597    fn shorthand_must_match_whole_input() {
598        let expression = "@dailyBla";
599        assert!(schedule(expression).is_err());
600        let expression = " @dailyBla ";
601        assert!(schedule(expression).is_err());
602    }
603}