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)), Err(_) => Err(ErrorKind::Expression("Invalid cron expression.".to_owned()).into()), }
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>, }
45
46trait FromField
47where
48 Self: Sized,
49{
50 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 #[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}