Skip to main content

awa_model/
job.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use sqlx::prelude::FromRow;
4use std::fmt;
5
6/// Job states in the lifecycle state machine.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)]
8#[sqlx(type_name = "awa.job_state", rename_all = "snake_case")]
9#[serde(rename_all = "snake_case")]
10pub enum JobState {
11    Scheduled,
12    Available,
13    Running,
14    Completed,
15    Retryable,
16    Failed,
17    Cancelled,
18}
19
20impl fmt::Display for JobState {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        match self {
23            JobState::Scheduled => write!(f, "scheduled"),
24            JobState::Available => write!(f, "available"),
25            JobState::Running => write!(f, "running"),
26            JobState::Completed => write!(f, "completed"),
27            JobState::Retryable => write!(f, "retryable"),
28            JobState::Failed => write!(f, "failed"),
29            JobState::Cancelled => write!(f, "cancelled"),
30        }
31    }
32}
33
34impl JobState {
35    /// Bit position for unique_states bitmask.
36    pub fn bit_position(&self) -> u8 {
37        match self {
38            JobState::Scheduled => 0,
39            JobState::Available => 1,
40            JobState::Running => 2,
41            JobState::Completed => 3,
42            JobState::Retryable => 4,
43            JobState::Failed => 5,
44            JobState::Cancelled => 6,
45        }
46    }
47
48    /// Check if this state is terminal (no further transitions possible).
49    pub fn is_terminal(&self) -> bool {
50        matches!(
51            self,
52            JobState::Completed | JobState::Failed | JobState::Cancelled
53        )
54    }
55}
56
57/// A row from the `awa.jobs` table.
58#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
59pub struct JobRow {
60    pub id: i64,
61    pub kind: String,
62    pub queue: String,
63    pub args: serde_json::Value,
64    pub state: JobState,
65    pub priority: i16,
66    pub attempt: i16,
67    pub max_attempts: i16,
68    pub run_at: DateTime<Utc>,
69    pub heartbeat_at: Option<DateTime<Utc>>,
70    pub deadline_at: Option<DateTime<Utc>>,
71    pub attempted_at: Option<DateTime<Utc>>,
72    pub finalized_at: Option<DateTime<Utc>>,
73    pub created_at: DateTime<Utc>,
74    pub errors: Option<Vec<serde_json::Value>>,
75    pub metadata: serde_json::Value,
76    pub tags: Vec<String>,
77    pub unique_key: Option<Vec<u8>>,
78    /// Unique states bitmask — stored as BIT(8) in Postgres.
79    /// Skipped in FromRow since it's only used by the DB-side unique index.
80    #[sqlx(skip)]
81    pub unique_states: Option<u8>,
82}
83
84/// Options for inserting a job.
85#[derive(Debug, Clone)]
86pub struct InsertOpts {
87    pub queue: String,
88    pub priority: i16,
89    pub max_attempts: i16,
90    pub run_at: Option<DateTime<Utc>>,
91    pub deadline_duration: Option<chrono::Duration>,
92    pub metadata: serde_json::Value,
93    pub tags: Vec<String>,
94    pub unique: Option<UniqueOpts>,
95}
96
97impl Default for InsertOpts {
98    fn default() -> Self {
99        Self {
100            queue: "default".to_string(),
101            priority: 2,
102            max_attempts: 25,
103            run_at: None,
104            deadline_duration: None,
105            metadata: serde_json::json!({}),
106            tags: Vec::new(),
107            unique: None,
108        }
109    }
110}
111
112/// Uniqueness constraint options.
113#[derive(Debug, Clone)]
114pub struct UniqueOpts {
115    /// Include queue in uniqueness calculation.
116    pub by_queue: bool,
117    /// Include args in uniqueness calculation.
118    pub by_args: bool,
119    /// Period bucket for time-based uniqueness (epoch seconds / period).
120    pub by_period: Option<i64>,
121    /// States in which uniqueness is enforced.
122    /// Default: scheduled, available, running, completed, retryable (bits 0-4).
123    pub states: u8,
124}
125
126impl Default for UniqueOpts {
127    fn default() -> Self {
128        Self {
129            by_queue: false,
130            by_args: true,
131            by_period: None,
132            // Default: bits 0-4 set (scheduled, available, running, completed, retryable)
133            states: 0b0001_1111,
134        }
135    }
136}
137
138impl UniqueOpts {
139    /// Convert the states bitmask to a BIT(8) representation for Postgres.
140    pub fn states_bits(&self) -> Vec<u8> {
141        vec![self.states]
142    }
143}
144
145/// Parameters for bulk insert.
146#[derive(Debug, Clone)]
147pub struct InsertParams {
148    pub kind: String,
149    pub args: serde_json::Value,
150    pub opts: InsertOpts,
151}