Skip to main content

ora_backend/
jobs.rs

1//! Job and related types.
2
3use std::time::{Duration, SystemTime};
4
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7
8use crate::{
9    common::{Label, LabelFilter, TimeRange},
10    executions::{ExecutionDetails, ExecutionId, ExecutionStatus},
11    executors::ExecutorId,
12    schedules::ScheduleId,
13};
14
15/// A job ID.
16#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
17#[repr(transparent)]
18pub struct JobId(pub Uuid);
19
20impl std::fmt::Display for JobId {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        write!(f, "{}", self.0)
23    }
24}
25
26impl From<Uuid> for JobId {
27    fn from(value: Uuid) -> Self {
28        Self(value)
29    }
30}
31
32impl From<JobId> for Uuid {
33    fn from(value: JobId) -> Self {
34        value.0
35    }
36}
37
38/// A Case-sensitive identifier for a job type.
39///
40/// The ID must conform to the following rules:
41///
42/// - An ID is made up of segments separated by dots (`.`).
43/// - Each segment must start with a letter (A-Z, a-z) and can be followed by
44///   letters, digits (0-9), or underscores (`_`).
45/// - The ID must not start or end with a dot, and must not contain consecutive dots.
46///
47/// Examples of valid IDs:
48/// - `data_processing`
49/// - `image.resize_v2`
50/// - `myService.analytics.GenerateReport`
51///
52#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
53#[repr(transparent)]
54#[serde(transparent)]
55pub struct JobTypeId(String);
56
57impl std::fmt::Display for JobTypeId {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        write!(f, "{}", self.0)
60    }
61}
62
63impl JobTypeId {
64    /// Create a new job type ID.
65    pub fn new<S: Into<String>>(s: S) -> Result<Self, InvalidJobTypeId> {
66        let s = s.into();
67
68        if !Self::validate(&s) {
69            return Err(InvalidJobTypeId(s));
70        }
71
72        Ok(Self(s))
73    }
74
75    /// Create a job type ID without validation.
76    ///
77    pub fn new_unchecked<S: Into<String>>(s: S) -> Self {
78        Self(s.into())
79    }
80
81    fn validate(s: &str) -> bool {
82        if s.is_empty() {
83            return false;
84        }
85
86        let segments = s.split('.');
87
88        for segment in segments {
89            if segment.is_empty() {
90                return false;
91            }
92
93            let mut chars = segment.bytes();
94
95            match chars.next() {
96                Some(c) if c.is_ascii_alphabetic() => {}
97                _ => return false,
98            }
99
100            for c in chars {
101                if !(c.is_ascii_alphanumeric() || c == b'_') {
102                    return false;
103                }
104            }
105        }
106
107        true
108    }
109
110    /// Get the job type ID as a string slice.
111    #[must_use]
112    pub fn as_str(&self) -> &str {
113        &self.0
114    }
115
116    /// Return the inner string.
117    #[must_use]
118    pub fn into_inner(self) -> String {
119        self.0
120    }
121}
122
123/// An error indicating that a job type ID is invalid.
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct InvalidJobTypeId(pub String);
126
127impl std::fmt::Display for InvalidJobTypeId {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        write!(f, r#"invalid job type ID: "{}""#, self.0)
130    }
131}
132
133impl core::error::Error for InvalidJobTypeId {}
134
135/// A job type.
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct JobType {
138    /// The ID of the job type.
139    pub id: JobTypeId,
140    /// An optional description of the job type.
141    pub description: Option<String>,
142    /// An optional input JSON schema.
143    pub input_schema_json: Option<String>,
144    /// An optional output JSON schema.
145    pub output_schema_json: Option<String>,
146}
147
148/// A job definition.
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct JobDefinition {
151    /// The job type ID.
152    pub job_type_id: JobTypeId,
153    /// The target execution time.
154    pub target_execution_time: SystemTime,
155    /// The job input payload JSON.
156    pub input_payload_json: String,
157    /// Labels associated with the job.
158    pub labels: Vec<Label>,
159    /// The timeout policy for the job.
160    pub timeout_policy: TimeoutPolicy,
161    /// The retry policy for the job.
162    pub retry_policy: RetryPolicy,
163}
164
165/// Details of a job.
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct JobDetails {
168    /// The job ID.
169    pub id: JobId,
170    /// The job definition.
171    pub job: JobDefinition,
172    /// The creation time of the job.
173    pub created_at: SystemTime,
174    /// The executions of the job.
175    pub executions: Vec<ExecutionDetails>,
176    /// The schedule the job belongs to.
177    pub schedule_id: Option<ScheduleId>,
178}
179
180/// Timeout policy for a job.
181#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct TimeoutPolicy {
183    /// The timeout duration.
184    ///
185    /// If zero, the job has no timeout.
186    pub timeout: Duration,
187    /// The base time of the timeout.
188    pub base_time: TimeoutBaseTime,
189}
190
191/// The base time of the timeout.
192#[derive(Debug, Clone, Serialize, Deserialize)]
193pub enum TimeoutBaseTime {
194    /// The timeout is measured from the job's target execution time.
195    TargetExecutionTime,
196    /// The timeout is measured from the actual start time of the job.
197    StartTime,
198}
199
200/// Retry policy for a job.
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct RetryPolicy {
203    /// The maximum number of retries.
204    ///
205    /// If zero, the job will not be retried.
206    pub retries: u64,
207}
208
209/// Filters for querying jobs.
210#[derive(Debug, Default, Clone, Serialize, Deserialize)]
211pub struct JobFilters {
212    /// Filter by job IDs.
213    pub job_ids: Option<Vec<JobId>>,
214    /// Filter by job type IDs.
215    pub job_type_ids: Option<Vec<JobTypeId>>,
216    /// Filter by executor IDs.
217    pub executor_ids: Option<Vec<ExecutorId>>,
218    /// Filter by execution IDs.
219    pub execution_ids: Option<Vec<ExecutionId>>,
220    /// Execution status.
221    pub execution_statuses: Option<Vec<ExecutionStatus>>,
222    /// Filter by target execution time range.
223    pub target_execution_time: Option<TimeRange>,
224    /// Filter by creation time range.
225    pub created_at: Option<TimeRange>,
226    /// Filter by labels.
227    pub labels: Option<Vec<LabelFilter>>,
228    /// Filter by schedule IDs.
229    pub schedule_ids: Option<Vec<ScheduleId>>,
230}
231
232/// The ordering options for listing jobs.
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub enum JobOrderBy {
235    /// Order by target execution time ascending.
236    TargetExecutionTimeAsc,
237    /// Order by target execution time descending.
238    TargetExecutionTimeDesc,
239    /// Order by creation time ascending.
240    CreatedAtAsc,
241    /// Order by creation time descending.
242    CreatedAtDesc,
243}
244
245/// A new job.
246pub struct NewJob {
247    /// The job definition.
248    pub job: JobDefinition,
249    /// The schedule the job should belong to.
250    pub schedule_id: Option<ScheduleId>,
251}
252
253/// A job that was cancelled.
254#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct CancelledJob {
256    /// The job ID.
257    pub job_id: JobId,
258    /// The last execution ID of the job.
259    pub last_execution_id: ExecutionId,
260}