Skip to main content

zart_api/
models.rs

1//! Request and response types for the Zart HTTP API.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use zart_scheduler::{ExecutionRecord, ExecutionSortField, ListExecutionsParams, SortOrder};
6
7// ── Requests ──────────────────────────────────────────────────────────────────
8
9/// Body for `POST /api/v1/executions`.
10#[derive(Debug, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct StartExecutionRequest {
13    /// Idempotency key. Generated as a UUID v4 when omitted.
14    #[serde(default)]
15    pub execution_id: Option<String>,
16    /// Registered task handler name.
17    pub task_name: String,
18    /// Arbitrary JSON payload forwarded to the task handler.
19    #[serde(default)]
20    pub payload: serde_json::Value,
21}
22
23/// Query parameters for `GET /api/v1/executions`.
24#[derive(Debug, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct ListQuery {
27    pub status: Option<String>,
28    pub task_name: Option<String>,
29    pub from: Option<DateTime<Utc>>,
30    pub to: Option<DateTime<Utc>>,
31    pub search: Option<String>,
32    pub sort_by: Option<String>,
33    pub sort_order: Option<String>,
34    #[serde(default = "default_limit")]
35    pub limit: usize,
36    #[serde(default)]
37    pub offset: usize,
38}
39
40impl ListQuery {
41    pub fn into_params(self) -> ListExecutionsParams {
42        let status = self
43            .status
44            .as_deref()
45            .and_then(|s| s.parse::<zart_scheduler::ExecutionStatus>().ok());
46        let sort_by = match self.sort_by.as_deref() {
47            Some("status") => ExecutionSortField::Status,
48            Some("taskName") => ExecutionSortField::TaskName,
49            _ => ExecutionSortField::ScheduledAt,
50        };
51        let sort_order = match self.sort_order.as_deref() {
52            Some("asc") => SortOrder::Asc,
53            _ => SortOrder::Desc,
54        };
55        ListExecutionsParams {
56            status,
57            task_name: self.task_name,
58            from: self.from,
59            to: self.to,
60            search: self.search,
61            sort_by,
62            sort_order,
63            limit: self.limit,
64            offset: self.offset,
65        }
66    }
67}
68
69fn default_limit() -> usize {
70    20
71}
72
73/// Query parameters for `GET /api/v1/executions/:id/wait`.
74#[derive(Debug, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub struct WaitQuery {
77    /// Maximum seconds to wait (capped at 30, default: 30).
78    pub timeout_secs: Option<u64>,
79}
80
81// ── Responses ─────────────────────────────────────────────────────────────────
82
83/// JSON representation of a durable execution record.
84#[derive(Debug, Serialize)]
85#[serde(rename_all = "camelCase")]
86pub struct ExecutionResponse {
87    /// Registered task handler name.
88    pub name: String,
89    /// Unique execution identifier (idempotency key).
90    pub durable_execution_id: String,
91    /// Original JSON payload.
92    pub payload: serde_json::Value,
93    /// Lifecycle status (scheduled | running | completed | failed | cancelled).
94    pub status: String,
95    /// When the execution was first scheduled.
96    pub scheduled_at: DateTime<Utc>,
97    /// When the execution reached a terminal state (`null` if still running).
98    pub completed_at: Option<DateTime<Utc>>,
99    /// Schema version counter.
100    pub version: i32,
101    /// JSON result produced by the task handler (`null` if not yet completed).
102    pub result: Option<serde_json::Value>,
103}
104
105impl From<ExecutionRecord> for ExecutionResponse {
106    fn from(r: ExecutionRecord) -> Self {
107        Self {
108            name: r.task_name,
109            durable_execution_id: r.execution_id,
110            payload: r.payload,
111            status: r.status.to_string(),
112            scheduled_at: r.scheduled_at,
113            completed_at: r.completed_at,
114            version: r.version,
115            result: r.result,
116        }
117    }
118}
119
120/// Body returned for a successful start.
121#[derive(Debug, Serialize)]
122#[serde(rename_all = "camelCase")]
123pub struct StartExecutionResponse {
124    pub execution_id: String,
125    pub task_id: String,
126}
127
128/// Body for error responses.
129#[derive(Debug, Serialize)]
130pub struct ErrorResponse {
131    pub error: String,
132}
133
134// ── Admin Requests ────────────────────────────────────────────────────────────
135
136/// Body for `POST /admin/v1/executions/:id/retry-step`.
137#[derive(Debug, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub struct RetryStepRequest {
140    pub step_name: String,
141    #[serde(default)]
142    pub triggered_by: Option<String>,
143}
144
145/// Body for `POST /admin/v1/executions/:id/restart`.
146#[derive(Debug, Deserialize)]
147#[serde(rename_all = "camelCase")]
148pub struct RestartRequest {
149    #[serde(default)]
150    pub payload: Option<serde_json::Value>,
151    #[serde(default)]
152    pub triggered_by: Option<String>,
153}
154
155/// Body for `POST /admin/v1/executions/:id/rerun`.
156#[derive(Debug, Deserialize)]
157#[serde(rename_all = "camelCase")]
158pub struct RerunRequest {
159    #[serde(default)]
160    pub rerun_steps: Vec<String>,
161    #[serde(default)]
162    pub preserve_steps: Vec<String>,
163    #[serde(default)]
164    pub triggered_by: Option<String>,
165}
166
167// ── Admin Responses ───────────────────────────────────────────────────────────
168
169/// Body returned for a successful retry-step.
170#[derive(Debug, Serialize)]
171#[serde(rename_all = "camelCase")]
172pub struct RetryStepResponse {
173    pub new_task_id: String,
174}
175
176/// Body returned for a successful restart.
177#[derive(Debug, Serialize)]
178#[serde(rename_all = "camelCase")]
179pub struct RestartResponse {
180    pub new_run_id: String,
181}
182
183/// Body returned for a successful rerun.
184#[derive(Debug, Serialize)]
185#[serde(rename_all = "camelCase")]
186pub struct RerunResponse {
187    pub new_run_number: u32,
188    pub effective_rerun: Vec<String>,
189}
190
191/// A single run record returned from the runs list.
192#[derive(Debug, Serialize)]
193#[serde(rename_all = "camelCase")]
194pub struct RunRecordResponse {
195    pub run_id: String,
196    pub execution_id: String,
197    pub run_index: i32,
198    pub payload: serde_json::Value,
199    pub status: String,
200    pub result: Option<serde_json::Value>,
201    pub started_at: DateTime<Utc>,
202    pub completed_at: Option<DateTime<Utc>>,
203    pub trigger: String,
204}
205
206// ── Pause / Resume Types ──────────────────────────────────────────────────────
207
208/// Body for `POST /admin/v1/pause`.
209#[derive(Debug, Deserialize)]
210#[serde(rename_all = "camelCase")]
211pub struct PauseRequest {
212    #[serde(default)]
213    pub execution_id: Option<String>,
214    #[serde(default)]
215    pub task_name: Option<String>,
216    #[serde(default)]
217    pub step_pattern: Option<String>,
218    #[serde(default)]
219    pub expires_at: Option<DateTime<Utc>>,
220    #[serde(default)]
221    pub triggered_by: Option<String>,
222}
223
224/// Response for a single pause rule.
225#[derive(Debug, Serialize)]
226#[serde(rename_all = "camelCase")]
227pub struct PauseRuleResponse {
228    pub rule_id: String,
229    #[serde(default)]
230    pub execution_id: Option<String>,
231    #[serde(default)]
232    pub task_name: Option<String>,
233    #[serde(default)]
234    pub step_pattern: Option<String>,
235    pub created_at: DateTime<Utc>,
236    #[serde(default)]
237    pub expires_at: Option<DateTime<Utc>>,
238    #[serde(default)]
239    pub created_by: Option<String>,
240    #[serde(default)]
241    pub deleted_at: Option<DateTime<Utc>>,
242}
243
244/// Response for a resume operation.
245#[derive(Debug, Serialize)]
246#[serde(rename_all = "camelCase")]
247pub struct ResumeResponse {
248    pub rules_deleted: usize,
249}
250
251/// Response for `GET /api/v1/stats`.
252#[derive(Debug, Serialize)]
253#[serde(rename_all = "camelCase")]
254pub struct StatsResponse {
255    pub scheduled: i64,
256    pub running: i64,
257    pub completed: i64,
258    pub failed: i64,
259    pub cancelled: i64,
260}
261
262impl From<zart_scheduler::ExecutionStats> for StatsResponse {
263    fn from(s: zart_scheduler::ExecutionStats) -> Self {
264        Self {
265            scheduled: s.scheduled,
266            running: s.running,
267            completed: s.completed,
268            failed: s.failed,
269            cancelled: s.cancelled,
270        }
271    }
272}
273
274/// Response for `GET /admin/v1/executions/:id/detail`.
275#[derive(Debug, Serialize)]
276#[serde(rename_all = "camelCase")]
277pub struct ExecutionDetailResponse {
278    pub execution: ExecutionResponse,
279    pub runs: Vec<RunRecordResponse>,
280    pub steps: Vec<StepDetailResponse>,
281}
282
283/// A step with its attempt history.
284#[derive(Debug, Serialize)]
285#[serde(rename_all = "camelCase")]
286pub struct StepDetailResponse {
287    pub step_id: String,
288    pub name: String,
289    pub kind: String,
290    pub status: String,
291    pub retry_attempt: i32,
292    pub result: Option<serde_json::Value>,
293    pub last_error: Option<String>,
294    pub retryable: bool,
295    pub scheduled_at: DateTime<Utc>,
296    pub completed_at: Option<DateTime<Utc>>,
297    pub attempts: Vec<StepAttemptResponse>,
298}
299
300/// A single step attempt in the detail response.
301#[derive(Debug, Serialize)]
302#[serde(rename_all = "camelCase")]
303pub struct StepAttemptResponse {
304    pub attempt_number: i32,
305    pub status: String,
306    pub result: Option<serde_json::Value>,
307    pub error: Option<String>,
308    pub started_at: DateTime<Utc>,
309    pub completed_at: Option<DateTime<Utc>>,
310}