use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use uuid::Uuid;
pub type JobId = Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum JobState {
Created,
Waiting,
Delayed,
Active,
Completed,
Failed,
Dlq,
Cancelled,
Blocked,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum BackoffStrategy {
Fixed,
Linear,
#[default]
Exponential,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueueOrdering {
#[default]
Fifo,
Lifo,
Priority,
Fair,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct LogEntry {
pub timestamp: DateTime<Utc>,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct Job {
#[schema(value_type = String, format = "uuid")]
pub id: JobId,
pub custom_id: Option<String>,
pub name: String,
pub queue: String,
pub data: serde_json::Value,
pub result: Option<serde_json::Value>,
pub state: JobState,
pub progress: Option<u8>,
pub logs: Vec<LogEntry>,
pub priority: i32,
pub delay_until: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub started_at: Option<DateTime<Utc>>,
pub completed_at: Option<DateTime<Utc>>,
pub max_attempts: u32,
pub attempt: u32,
pub backoff: BackoffStrategy,
pub backoff_delay_ms: u64,
pub last_error: Option<String>,
pub ttl_ms: Option<u64>,
pub timeout_ms: Option<u64>,
pub unique_key: Option<String>,
pub tags: Vec<String>,
pub group_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
#[schema(value_type = Vec<String>)]
pub depends_on: Vec<JobId>,
pub flow_id: Option<String>,
pub lifo: bool,
pub remove_on_complete: bool,
pub remove_on_fail: bool,
pub worker_id: Option<String>,
pub last_heartbeat: Option<DateTime<Utc>>,
}
impl Job {
pub fn new(queue: impl Into<String>, name: impl Into<String>, data: serde_json::Value) -> Self {
let now = Utc::now();
Self {
id: Uuid::now_v7(),
custom_id: None,
name: name.into(),
queue: queue.into(),
data,
result: None,
state: JobState::Waiting,
progress: None,
logs: Vec::new(),
priority: 0,
delay_until: None,
created_at: now,
updated_at: now,
started_at: None,
completed_at: None,
max_attempts: 3,
attempt: 0,
backoff: BackoffStrategy::default(),
backoff_delay_ms: 1000,
last_error: None,
ttl_ms: None,
timeout_ms: None,
unique_key: None,
tags: Vec::new(),
group_id: None,
metadata: None,
depends_on: Vec::new(),
flow_id: None,
lifo: false,
remove_on_complete: false,
remove_on_fail: false,
worker_id: None,
last_heartbeat: None,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema)]
pub struct QueueCounts {
pub waiting: u64,
pub active: u64,
pub delayed: u64,
pub completed: u64,
pub failed: u64,
pub dlq: u64,
pub blocked: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct Schedule {
pub name: String,
pub queue: String,
pub job_name: String,
pub job_data: serde_json::Value,
#[serde(default)]
pub job_options: Option<crate::engine::queue::JobOptions>,
pub cron_expr: Option<String>,
pub every_ms: Option<u64>,
pub timezone: Option<String>,
pub max_executions: Option<u64>,
pub execution_count: u64,
pub paused: bool,
pub last_run_at: Option<DateTime<Utc>>,
pub next_run_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_job_new_defaults() {
let job = Job::new(
"emails",
"send-welcome",
serde_json::json!({"to": "a@b.com"}),
);
assert_eq!(job.queue, "emails");
assert_eq!(job.name, "send-welcome");
assert_eq!(job.state, JobState::Waiting);
assert_eq!(job.priority, 0);
assert_eq!(job.max_attempts, 3);
assert_eq!(job.attempt, 0);
assert_eq!(job.backoff, BackoffStrategy::Exponential);
assert!(!job.lifo);
assert!(job.depends_on.is_empty());
}
#[test]
fn test_job_serialization_roundtrip() {
let job = Job::new("test", "test-job", serde_json::json!({"key": "value"}));
let json = serde_json::to_string(&job).unwrap();
let deserialized: Job = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.id, job.id);
assert_eq!(deserialized.queue, job.queue);
assert_eq!(deserialized.state, job.state);
}
#[test]
fn test_job_state_serde() {
let state = JobState::Waiting;
let json = serde_json::to_string(&state).unwrap();
assert_eq!(json, "\"waiting\"");
let deserialized: JobState = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, JobState::Waiting);
}
#[test]
fn test_backoff_strategy_default() {
assert_eq!(BackoffStrategy::default(), BackoffStrategy::Exponential);
}
}