use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use crate::Result;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum JobStatus {
Pending,
Running,
Completed,
Failed,
Retrying,
DeadLetter, }
pub type JobResult = Result<()>;
#[async_trait]
pub trait Job: Send + Sync + Serialize + for<'de> Deserialize<'de> {
async fn perform(&self) -> JobResult;
fn max_retries(&self) -> u32 {
3
}
fn backoff(&self, attempt: u32) -> Duration {
Duration::from_secs(60 * 2_u64.pow(attempt))
}
fn priority(&self) -> i32 {
0
}
fn name(&self) -> &'static str {
std::any::type_name::<Self>()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JobWrapper {
pub id: String,
pub name: String,
pub payload: serde_json::Value,
pub status: JobStatus,
pub attempts: u32,
pub max_retries: u32,
pub created_at: i64,
pub scheduled_at: Option<i64>,
pub priority: i32,
pub cron_schedule: Option<String>, pub last_run_at: Option<i64>, pub error: Option<String>, }
impl JobWrapper {
pub fn new<J: Job>(job: &J) -> Result<Self> {
let payload = serde_json::to_value(job)?;
let now = chrono::Utc::now().timestamp();
Ok(Self {
id: uuid::Uuid::new_v4().to_string(),
name: job.name().to_string(),
payload,
status: JobStatus::Pending,
attempts: 0,
max_retries: job.max_retries(),
created_at: now,
scheduled_at: None,
priority: job.priority(),
cron_schedule: None,
last_run_at: None,
error: None,
})
}
pub fn with_delay(mut self, delay: Duration) -> Self {
let scheduled_time = chrono::Utc::now().timestamp() + delay.as_secs() as i64;
self.scheduled_at = Some(scheduled_time);
self
}
pub fn with_cron(mut self, cron_expr: String) -> Self {
self.scheduled_at = Self::next_cron_run(&cron_expr);
self.cron_schedule = Some(cron_expr);
self
}
fn next_cron_run(cron_expr: &str) -> Option<i64> {
use cron::Schedule;
use std::str::FromStr;
if let Ok(schedule) = Schedule::from_str(cron_expr) {
if let Some(next) = schedule.upcoming(chrono::Utc).next() {
return Some(next.timestamp());
}
}
None
}
pub fn reschedule(&mut self) {
if let Some(cron_expr) = &self.cron_schedule {
self.scheduled_at = Self::next_cron_run(cron_expr);
self.status = JobStatus::Pending;
self.attempts = 0;
self.last_run_at = Some(chrono::Utc::now().timestamp());
}
}
pub fn should_retry(&self) -> bool {
self.status == JobStatus::Failed && self.attempts < self.max_retries
}
pub fn calculate_backoff(&self) -> Duration {
Duration::from_secs(60 * 2_u64.pow(self.attempts))
}
}