Skip to main content

cron/
parsing.rs

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