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