use crate::{BoxFuture, Handler};
use cron::Schedule as CronSchedule;
use sova_tasks_store::Task;
use serde_json::Value;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
pub mod priority {
pub const LOW: i32 = -100;
pub const NORMAL: i32 = 0;
pub const HIGH: i32 = 100;
}
#[derive(Clone, Debug)]
#[allow(clippy::large_enum_variant)] pub enum Schedule {
Every(Duration),
Cron(CronSchedule),
}
pub struct Job {
pub(crate) name: String,
pub(crate) handler: Handler,
pub(crate) schedule: Option<Schedule>,
pub(crate) payload: Value,
pub(crate) queue: Option<String>,
pub(crate) priority: i32,
}
impl Job {
pub fn new<F, Fut>(name: impl Into<String>, f: F) -> Self
where
F: Fn(Task) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Result<(), String>> + Send + 'static,
{
Self {
name: name.into(),
handler: Arc::new(move |t| Box::pin(f(t)) as BoxFuture<Result<(), String>>),
schedule: None,
payload: Value::Object(Default::default()),
queue: None,
priority: priority::NORMAL,
}
}
pub fn queue(mut self, q: impl Into<String>) -> Self {
self.queue = Some(q.into());
self
}
pub fn priority(mut self, p: i32) -> Self {
self.priority = p;
self
}
pub fn every(mut self, period: Duration) -> Self {
assert!(!period.is_zero(), "Job::every period must be non-zero");
self.schedule = Some(Schedule::Every(period));
self
}
pub fn cron(mut self, expr: impl AsRef<str>) -> Self {
let sched = parse_cron(expr.as_ref())
.unwrap_or_else(|e| panic!("Job::cron({}): {e}", expr.as_ref()));
self.schedule = Some(Schedule::Cron(sched));
self
}
pub fn payload(mut self, data: Value) -> Self {
self.payload = data;
self
}
}
pub fn parse_cron(expr: &str) -> Result<CronSchedule, String> {
let trimmed = expr.trim();
let fields = trimmed.split_whitespace().count();
let padded = if fields == 5 {
format!("0 {trimmed}")
} else {
trimmed.to_string()
};
CronSchedule::from_str(&padded).map_err(|e| e.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pads_five_field_cron() {
let s = parse_cron("0 * * * *").unwrap();
assert!(s.upcoming(chrono::Utc).next().is_some());
}
#[test]
#[should_panic(expected = "Job::cron")]
fn bad_cron_panics() {
let _ = Job::new("x", |_| async { Ok(()) }).cron("not a cron");
}
}