use crate::core::JobState;
use crate::error::{QmlError, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Job {
pub id: String,
pub method: String,
pub payload: JsonValue,
pub created_at: DateTime<Utc>,
pub state: JobState,
pub queue: String,
pub priority: i32,
pub max_retries: u32,
pub attempt: u32,
pub metadata: HashMap<String, String>,
pub job_type: Option<String>,
pub timeout_seconds: Option<u64>,
#[serde(default)]
pub expires_at: Option<DateTime<Utc>>,
}
impl Job {
pub fn new(method: impl Into<String>, payload: JsonValue) -> Self {
let id = Uuid::new_v4().to_string();
let now = Utc::now();
Self {
id,
method: method.into(),
payload,
created_at: now,
state: JobState::enqueued("default"),
queue: "default".to_string(),
priority: 0,
max_retries: 0,
attempt: 0,
metadata: HashMap::new(),
job_type: None,
timeout_seconds: None,
expires_at: None,
}
}
pub fn new_typed<A: Serialize>(method: impl Into<String>, args: &A) -> Result<Self> {
let payload = serde_json::to_value(args).map_err(|e| QmlError::SerializationError {
message: format!("Failed to serialize job payload: {}", e),
})?;
Ok(Self::new(method, payload))
}
pub fn with_config(
method: impl Into<String>,
payload: JsonValue,
queue: impl Into<String>,
priority: i32,
max_retries: u32,
) -> Self {
let id = Uuid::new_v4().to_string();
let now = Utc::now();
let queue = queue.into();
Self {
id,
method: method.into(),
payload,
created_at: now,
state: JobState::enqueued(&queue),
queue,
priority,
max_retries,
attempt: 0,
metadata: HashMap::new(),
job_type: None,
timeout_seconds: None,
expires_at: None,
}
}
pub fn serialize(&self) -> Result<String> {
serde_json::to_string(self).map_err(|e| QmlError::SerializationError {
message: format!("Failed to serialize job: {}", e),
})
}
pub fn deserialize(json: &str) -> Result<Self> {
serde_json::from_str(json).map_err(|e| QmlError::SerializationError {
message: format!("Failed to deserialize job: {}", e),
})
}
pub fn set_state(&mut self, new_state: JobState) -> Result<()> {
if !self.state.can_transition_to(&new_state) {
return Err(QmlError::InvalidStateTransition {
from: format!("{:?}", self.state),
to: format!("{:?}", new_state),
});
}
self.state = new_state;
Ok(())
}
pub fn add_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.metadata.insert(key.into(), value.into());
}
pub fn set_type(&mut self, job_type: impl Into<String>) {
self.job_type = Some(job_type.into());
}
pub fn set_timeout(&mut self, timeout_seconds: u64) {
self.timeout_seconds = Some(timeout_seconds);
}
pub fn age_seconds(&self) -> i64 {
let now = Utc::now();
now.signed_duration_since(self.created_at).num_seconds()
}
pub fn is_timed_out(&self) -> bool {
if let Some(timeout) = self.timeout_seconds {
self.age_seconds() > timeout as i64
} else {
false
}
}
pub fn clone_with_new_id(&self) -> Self {
let mut cloned = self.clone();
cloned.id = Uuid::new_v4().to_string();
cloned.created_at = Utc::now();
cloned.attempt = 0;
cloned.expires_at = None;
cloned
}
}