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}