Skip to main content

ironflow_api/entities/
schedule.rs

1//! Schedule request and response DTOs.
2
3use chrono::{DateTime, Utc};
4use chrono_tz::Tz;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use uuid::Uuid;
8use validator::Validate;
9
10use ironflow_store::entities::{CatchupPolicy, OverlapPolicy, Schedule, ScheduleSource};
11
12/// Schedule list/detail response.
13#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
14#[derive(Debug, Serialize)]
15pub struct ScheduleResponse {
16    /// Schedule ID.
17    pub id: Uuid,
18    /// Name of the workflow to trigger.
19    pub workflow_name: String,
20    /// Cron expression.
21    pub cron_expression: String,
22    /// JSON payload passed to the workflow.
23    pub inputs: Value,
24    /// Where this schedule was created (`handler` or `api`).
25    pub source: ScheduleSource,
26    /// When the schedule was disabled. `None` means active.
27    pub disabled_at: Option<DateTime<Utc>>,
28    /// When the schedule last created a run.
29    pub last_triggered_at: Option<DateTime<Utc>>,
30    /// When the schedule will next fire. Always set on an active schedule.
31    pub next_trigger_at: Option<DateTime<Utc>>,
32    /// Why Ironflow disabled the schedule on its own (e.g. its next trigger
33    /// cannot be computed). `None` when paused by a user or never disabled.
34    pub last_error: Option<String>,
35    /// Queue priority given to every run the schedule creates, from -100 to 100.
36    pub priority: i16,
37    /// What the schedule does with the occurrences it missed while no server
38    /// fired it.
39    pub catchup: CatchupPolicy,
40    /// Most runs created to catch up under `catchup = all`.
41    pub catchup_max: u32,
42    /// How far back, in seconds, a missed occurrence is still caught up.
43    pub catchup_window_secs: u32,
44    /// What the schedule does when an occurrence comes while one of its runs
45    /// is still active.
46    pub overlap: OverlapPolicy,
47    /// IANA timezone the cron expression is evaluated in.
48    #[cfg_attr(feature = "openapi", schema(value_type = String))]
49    pub timezone: Tz,
50    /// User who created the schedule. `None` for handler-declared schedules.
51    pub created_by_user_id: Option<Uuid>,
52    /// When the schedule was created.
53    pub created_at: DateTime<Utc>,
54    /// When the schedule was last updated.
55    pub updated_at: DateTime<Utc>,
56}
57
58impl From<Schedule> for ScheduleResponse {
59    fn from(s: Schedule) -> Self {
60        Self {
61            id: s.id,
62            workflow_name: s.workflow_name,
63            cron_expression: s.cron_expression,
64            inputs: s.inputs,
65            source: s.source,
66            disabled_at: s.disabled_at,
67            last_triggered_at: s.last_triggered_at,
68            next_trigger_at: s.next_trigger_at,
69            last_error: s.last_error,
70            priority: s.priority,
71            catchup: s.policy.catchup,
72            catchup_max: s.policy.catchup_max,
73            catchup_window_secs: s.policy.catchup_window_secs,
74            overlap: s.policy.overlap,
75            timezone: s.policy.timezone,
76            created_by_user_id: s.created_by_user_id,
77            created_at: s.created_at,
78            updated_at: s.updated_at,
79        }
80    }
81}
82
83/// Create schedule request body.
84#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
85#[derive(Debug, Deserialize, Validate)]
86pub struct CreateScheduleRequest {
87    /// Name of the workflow to trigger.
88    #[validate(length(min = 1, message = "workflow_name must not be empty"))]
89    pub workflow_name: String,
90    /// Cron expression (6-field format, e.g. `"0 */5 * * * *"`).
91    #[validate(length(min = 1, message = "cron_expression must not be empty"))]
92    pub cron_expression: String,
93    /// JSON payload for the workflow. Defaults to `{}`.
94    #[serde(default = "default_inputs")]
95    pub inputs: Value,
96    /// Queue priority given to every run the schedule creates, from -100 to
97    /// 100. Defaults to the priority the workflow handler declares.
98    ///
99    /// A higher priority run is picked first by workers. A running run is
100    /// never preempted, and a low priority run is not aged.
101    #[serde(default)]
102    pub priority: Option<i16>,
103    /// What the schedule does with the occurrences it missed while no server
104    /// fired it: `latest` (default) runs the most recent one, `all` runs each
105    /// of them, `skip` runs none.
106    #[serde(default)]
107    pub catchup: Option<CatchupPolicy>,
108    /// Most runs created to catch up under `catchup = all`, from 1 to 1000.
109    /// Defaults to 10.
110    #[serde(default)]
111    pub catchup_max: Option<u32>,
112    /// How far back, in seconds, a missed occurrence is still caught up, from
113    /// 60 to 2592000 (30 days). Defaults to 86400 (one day).
114    #[serde(default)]
115    pub catchup_window_secs: Option<u32>,
116    /// What the schedule does when an occurrence comes while one of its runs
117    /// is still active: `allow` (default) starts another run, `skip` drops the
118    /// occurrence.
119    #[serde(default)]
120    pub overlap: Option<OverlapPolicy>,
121    /// IANA timezone the cron expression is evaluated in, e.g.
122    /// `"Europe/Paris"`. Defaults to `"UTC"`.
123    #[serde(default)]
124    #[cfg_attr(feature = "openapi", schema(value_type = Option<String>))]
125    pub timezone: Option<Tz>,
126}
127
128fn default_inputs() -> Value {
129    Value::Object(serde_json::Map::new())
130}
131
132/// Update schedule request body. All fields optional.
133#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
134#[derive(Debug, Deserialize)]
135pub struct UpdateScheduleRequest {
136    /// New cron expression.
137    pub cron_expression: Option<String>,
138    /// New inputs payload.
139    pub inputs: Option<Value>,
140}