use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct JobId(String);
impl JobId {
pub fn new() -> Self {
let ts = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
Self(format!("{:x}", ts))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for JobId {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum JobState {
Pending,
Validating,
Validated,
Executing,
Completed,
Failed,
RolledBack,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Job {
pub id: JobId,
pub capability: String,
pub args: serde_json::Value,
pub state: JobState,
pub created_at: u64,
pub updated_at: u64,
pub output: Option<serde_json::Value>,
pub error: Option<String>,
pub dry_run: bool,
}
impl Job {
pub fn new(capability: String, args: serde_json::Value, dry_run: bool) -> Self {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
Self {
id: JobId::new(),
capability,
args,
state: JobState::Pending,
created_at: now,
updated_at: now,
output: None,
error: None,
dry_run,
}
}
#[allow(clippy::match_like_matches_macro)]
pub fn transition_to(&mut self, new_state: JobState) -> Result<(), String> {
let valid = matches!(
(self.state, new_state),
(JobState::Pending, JobState::Validating)
| (JobState::Validating, JobState::Validated)
| (JobState::Validating, JobState::Failed)
| (JobState::Validated, JobState::Executing)
| (JobState::Executing, JobState::Completed)
| (JobState::Executing, JobState::Failed)
| (JobState::Completed, JobState::RolledBack)
);
if valid {
self.state = new_state;
self.updated_at = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
Ok(())
} else {
Err(format!(
"Invalid state transition: {:?} -> {:?}",
self.state, new_state
))
}
}
}