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)), 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 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 #[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}