use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use crate::segment::{parse_field, Kind, Segment};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum CronError {
Empty,
FieldCount {
expected: usize,
got: usize,
},
InvalidStep(String),
InvalidField(String),
}
impl fmt::Display for CronError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CronError::Empty => f.write_str("empty cron expression"),
CronError::FieldCount { expected, got } => {
write!(f, "expected {expected} fields, found {got}")
}
CronError::InvalidStep(s) => write!(f, "invalid step: {s:?}"),
CronError::InvalidField(s) => write!(f, "invalid field: {s:?}"),
}
}
}
impl core::error::Error for CronError {}
pub(crate) struct Schedule {
pub minute: Vec<Segment>,
pub hour: Vec<Segment>,
pub dom: Vec<Segment>,
pub month: Vec<Segment>,
pub dow: Vec<Segment>,
}
pub(crate) fn parse(expr: &str) -> Result<Schedule, CronError> {
let trimmed = expr.trim();
if trimmed.is_empty() {
return Err(CronError::Empty);
}
let expanded = expand_macro(trimmed).unwrap_or(trimmed);
let fields: Vec<&str> = expanded.split_whitespace().collect();
if fields.len() != 5 {
return Err(CronError::FieldCount {
expected: 5,
got: fields.len(),
});
}
Ok(Schedule {
minute: parse_field(fields[0], Kind::Minute)?,
hour: parse_field(fields[1], Kind::Hour)?,
dom: parse_field(fields[2], Kind::Dom)?,
month: parse_field(fields[3], Kind::Month)?,
dow: parse_field(fields[4], Kind::Dow)?,
})
}
fn expand_macro(s: &str) -> Option<&'static str> {
match s {
"@yearly" | "@annually" => Some("0 0 1 1 *"),
"@monthly" => Some("0 0 1 * *"),
"@weekly" => Some("0 0 * * 0"),
"@daily" | "@midnight" => Some("0 0 * * *"),
"@hourly" => Some("0 * * * *"),
_ => None,
}
}