use cron::Schedule;
use std::str::FromStr;
use super::task_schedule::ScheduledTask;
pub struct CronTrigger {
schedule: Schedule,
task: ScheduledTask,
}
impl CronTrigger {
pub fn new(task: ScheduledTask) -> anyhow::Result<Self> {
let expr = if task.cron_expression.split_whitespace().count() == 5 {
format!("0 {}", task.cron_expression)
} else {
task.cron_expression.clone()
};
let schedule = Schedule::from_str(&expr).map_err(|e| {
anyhow::anyhow!("Invalid cron expression '{}': {}", task.cron_expression, e)
})?;
Ok(Self { schedule, task })
}
pub fn next_fire(&self) -> Option<chrono::DateTime<chrono::Utc>> {
self.schedule.upcoming(chrono::Utc).next()
}
pub fn duration_until_next(&self) -> Option<std::time::Duration> {
self.next_fire().map(|next| {
let now = chrono::Utc::now();
if next > now {
(next - now)
.to_std()
.unwrap_or(std::time::Duration::from_secs(1))
} else {
std::time::Duration::from_secs(0)
}
})
}
pub fn task(&self) -> &ScheduledTask {
&self.task
}
pub fn task_id(&self) -> &str {
&self.task.id
}
}
#[cfg(test)]
mod tests {
use super::super::task_schedule::ScheduledTaskType;
use super::*;
#[test]
fn cron_trigger_parses_five_field_expression() {
let task = ScheduledTask::new(
"test".to_string(),
"Test".to_string(),
"*/5 * * * *".to_string(),
ScheduledTaskType::CodeQualityCheck {
repo_path: ".".to_string(),
},
);
let trigger = CronTrigger::new(task).unwrap();
assert!(trigger.next_fire().is_some());
}
#[test]
fn cron_trigger_rejects_invalid_expression() {
let task = ScheduledTask::new(
"test".to_string(),
"Test".to_string(),
"not a cron".to_string(),
ScheduledTaskType::CodeQualityCheck {
repo_path: ".".to_string(),
},
);
assert!(CronTrigger::new(task).is_err());
}
#[test]
fn duration_until_next_returns_some() {
let task = ScheduledTask::new(
"test".to_string(),
"Test".to_string(),
"* * * * *".to_string(), ScheduledTaskType::CodeQualityCheck {
repo_path: ".".to_string(),
},
);
let trigger = CronTrigger::new(task).unwrap();
assert!(trigger.duration_until_next().is_some());
}
}