use super::{BackOff, BackOffJobOptions};
use chrono::{TimeDelta, Utc};
use croner::{errors::CronError, Cron};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Debug, Serialize, Deserialize, Clone, Hash, PartialEq)]
pub enum Repeat {
WithCron(Box<Cron>),
WithBackOff(BackOffJobOptions),
Every {
delay_ms: i64,
max_attempts: Option<u64>,
},
Immediately(u64),
}
impl Repeat {
pub fn from_cron_str(pattern: &str) -> Result<Self, CronError> {
let cron = Cron::from_str(pattern)?;
Ok(Self::WithCron(Box::new(cron)))
}
#[must_use]
pub const fn from_back_off(opts: BackOffJobOptions) -> Self {
Self::WithBackOff(opts)
}
#[must_use]
pub const fn repeat_every_for_times(every_ms: i64, max_attempts: Option<u64>) -> Self {
Self::Every {
delay_ms: every_ms,
max_attempts,
}
}
#[must_use]
pub fn next_occurrence(&self, backoff: &BackOff, attempts: u64) -> Option<i64> {
let now = Utc::now();
match self {
Self::WithCron(cron) => cron
.find_next_occurrence(&now, false)
.ok()
.map(|dt| dt.timestamp_millis()),
Self::WithBackOff(opts) => {
let opts = BackOff::normalize(Some(opts))?;
let delay_fn = backoff.lookup_strategy(opts, None)?;
let next_delay_ms = delay_fn(attempts.cast_signed());
let next_dt = now + TimeDelta::milliseconds(next_delay_ms);
Some(next_dt.timestamp_millis())
}
Self::Every {
delay_ms,
max_attempts,
} => {
if let Some(max_ts) = max_attempts {
if attempts >= *max_ts {
return None;
}
}
let next_dt = now + TimeDelta::milliseconds(*delay_ms);
Some(next_dt.timestamp_millis())
}
Self::Immediately(max_attempts) => {
if attempts >= *max_attempts {
return None;
}
Some(0)
}
}
}
}
impl From<BackOffJobOptions> for Repeat {
fn from(value: BackOffJobOptions) -> Self {
Self::from_back_off(value)
}
}
impl From<(i64, Option<u64>)> for Repeat {
fn from(value: (i64, Option<u64>)) -> Self {
Self::Every {
delay_ms: value.0,
max_attempts: value.1,
}
}
}
impl TryFrom<&str> for Repeat {
type Error = CronError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::from_cron_str(value)
}
}