use chrono::Weekday;
use graphile_worker_crontab_types::{
Crontab, CrontabFill, CrontabTimer, CrontabTimerError, JobKeyMode,
};
use graphile_worker_task_handler::TaskHandler;
use serde_json::Value;
use std::marker::PhantomData;
pub struct Cron;
#[derive(Debug, Clone)]
pub struct CronBuilder<T: TaskHandler> {
crontab: Crontab,
_task: PhantomData<fn() -> T>,
}
impl Cron {
pub fn from_timer<T: TaskHandler>(timer: CrontabTimer) -> CronBuilder<T> {
CronBuilder::new(timer)
}
pub fn every_minute<T: TaskHandler>() -> CronBuilder<T> {
Self::from_timer(CrontabTimer::every_minute())
}
pub fn every_n_minutes<T: TaskHandler>(step: u32) -> Result<CronBuilder<T>, CrontabTimerError> {
Ok(Self::from_timer(CrontabTimer::every_n_minutes(step)?))
}
pub fn hourly_at<T: TaskHandler>(minute: u32) -> Result<CronBuilder<T>, CrontabTimerError> {
Ok(Self::from_timer(CrontabTimer::hourly_at(minute)?))
}
pub fn daily_at<T: TaskHandler>(
hour: u32,
minute: u32,
) -> Result<CronBuilder<T>, CrontabTimerError> {
Ok(Self::from_timer(CrontabTimer::daily_at(hour, minute)?))
}
pub fn weekly_on<T: TaskHandler>(
weekday: Weekday,
hour: u32,
minute: u32,
) -> Result<CronBuilder<T>, CrontabTimerError> {
Ok(Self::from_timer(CrontabTimer::weekly_on(
weekday, hour, minute,
)?))
}
pub fn monthly_on<T: TaskHandler>(
day: u32,
hour: u32,
minute: u32,
) -> Result<CronBuilder<T>, CrontabTimerError> {
Ok(Self::from_timer(CrontabTimer::monthly_on(
day, hour, minute,
)?))
}
pub fn yearly_on<T: TaskHandler>(
month: u32,
day: u32,
hour: u32,
minute: u32,
) -> Result<CronBuilder<T>, CrontabTimerError> {
Ok(Self::from_timer(CrontabTimer::yearly_on(
month, day, hour, minute,
)?))
}
}
impl<T: TaskHandler> CronBuilder<T> {
pub fn new(timer: CrontabTimer) -> Self {
Self {
crontab: Crontab::new(timer, T::IDENTIFIER),
_task: PhantomData,
}
}
pub fn id(mut self, id: impl Into<String>) -> Self {
self.crontab.options.id = Some(id.into());
self
}
pub fn fill(mut self, fill: CrontabFill) -> Self {
self.crontab.options.fill = Some(fill);
self
}
pub fn max_attempts(mut self, max_attempts: u16) -> Self {
self.crontab.options.max = Some(max_attempts);
self
}
pub fn queue(mut self, queue: impl Into<String>) -> Self {
self.crontab.options.queue = Some(queue.into());
self
}
pub fn priority(mut self, priority: i16) -> Self {
self.crontab.options.priority = Some(priority);
self
}
pub fn job_key(mut self, job_key: impl Into<String>) -> Self {
self.crontab.options.job_key = Some(job_key.into());
self
}
pub fn job_key_mode(mut self, job_key_mode: JobKeyMode) -> Self {
self.crontab.options.job_key_mode = Some(job_key_mode);
self
}
pub fn payload(mut self, payload: T) -> Result<Self, serde_json::Error> {
self.crontab.payload = Some(serde_json::to_value(payload)?);
Ok(self)
}
pub fn payload_value(mut self, payload: impl Into<Value>) -> Self {
self.crontab.payload = Some(payload.into());
self
}
pub fn build(self) -> Crontab {
self.crontab
}
}
impl<T: TaskHandler> From<CronBuilder<T>> for Crontab {
fn from(builder: CronBuilder<T>) -> Self {
builder.build()
}
}
#[cfg(test)]
mod tests {
use super::*;
use graphile_worker_ctx::WorkerContext;
use graphile_worker_task_handler::IntoTaskHandlerResult;
use serde::{Deserialize, Serialize};
use serde_json::json;
#[derive(Deserialize, Serialize)]
struct SendDigest {
message: String,
}
impl TaskHandler for SendDigest {
const IDENTIFIER: &'static str = "send_digest";
async fn run(self, _ctx: WorkerContext) -> impl IntoTaskHandlerResult {}
}
#[test]
fn cron_builder_uses_task_identifier_and_options() {
let crontab = Cron::daily_at::<SendDigest>(8, 30)
.unwrap()
.id("daily_digest")
.fill(CrontabFill::hours(2))
.max_attempts(3)
.queue("mail")
.priority(-1)
.job_key("daily_digest")
.job_key_mode(JobKeyMode::PreserveRunAt)
.payload(SendDigest {
message: "hello".to_string(),
})
.unwrap()
.build();
assert_eq!(crontab.task_identifier().as_str(), "send_digest");
assert_eq!(crontab.identifier(), "daily_digest");
assert_eq!(
crontab.timer().hours(),
&vec![graphile_worker_crontab_types::CrontabValue::Number(8)]
);
assert_eq!(
crontab.timer().minutes(),
&vec![graphile_worker_crontab_types::CrontabValue::Number(30)]
);
assert_eq!(crontab.options().fill(), &Some(CrontabFill::hours(2)));
assert_eq!(crontab.options().max(), &Some(3));
assert_eq!(crontab.options().queue(), &Some("mail".to_string()));
assert_eq!(crontab.options().priority(), &Some(-1));
assert_eq!(
crontab.options().job_key(),
&Some("daily_digest".to_string())
);
assert_eq!(
crontab.options().job_key_mode(),
&Some(JobKeyMode::PreserveRunAt)
);
assert_eq!(crontab.payload(), &Some(json!({ "message": "hello" })));
}
#[test]
fn cron_builder_converts_into_crontab() {
let crontab: Crontab = Cron::every_minute::<SendDigest>().into();
assert_eq!(crontab.task_identifier().as_str(), "send_digest");
assert_eq!(crontab.timer(), &CrontabTimer::every_minute());
}
#[test]
fn cron_convenience_constructors_build_expected_timers() {
let every_n = Cron::every_n_minutes::<SendDigest>(15).unwrap().build();
assert_eq!(every_n.timer(), &CrontabTimer::every_n_minutes(15).unwrap());
let hourly = Cron::hourly_at::<SendDigest>(10).unwrap().build();
assert_eq!(hourly.timer(), &CrontabTimer::hourly_at(10).unwrap());
let daily = Cron::daily_at::<SendDigest>(8, 30).unwrap().build();
assert_eq!(daily.timer(), &CrontabTimer::daily_at(8, 30).unwrap());
let weekly = Cron::weekly_on::<SendDigest>(Weekday::Mon, 8, 30)
.unwrap()
.build();
assert_eq!(
weekly.timer(),
&CrontabTimer::weekly_on(Weekday::Mon, 8, 30).unwrap()
);
let monthly = Cron::monthly_on::<SendDigest>(1, 8, 30).unwrap().build();
assert_eq!(
monthly.timer(),
&CrontabTimer::monthly_on(1, 8, 30).unwrap()
);
let yearly = Cron::yearly_on::<SendDigest>(1, 1, 8, 30)
.unwrap()
.payload_value(json!({ "message": "manual" }))
.build();
assert_eq!(
yearly.timer(),
&CrontabTimer::yearly_on(1, 1, 8, 30).unwrap()
);
assert_eq!(yearly.payload(), &Some(json!({ "message": "manual" })));
}
}