Skip to main content

chronon_axum/
dto.rs

1//! JSON request and response types for the Chronon HTTP API.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use chronon_core::{Job, Run, ScheduleKind};
7
8/// Body for `POST /jobs/upsert`.
9///
10/// Creates a new job or updates fields on an existing job matched by `job_name`.
11#[derive(Debug, Deserialize, Serialize)]
12pub struct UpsertJobRequest {
13    /// Unique display name; used to locate an existing job on upsert.
14    pub job_name: String,
15    /// Registered script name; must exist in the host [`ScriptRegistry`](chronon_executor::ScriptRegistry).
16    pub script_name: String,
17    /// Cron expression when `schedule_kind` is [`ScheduleKindDto::Cron`].
18    pub cron_expr: Option<String>,
19    /// IANA timezone for cron evaluation; defaults to UTC when omitted.
20    pub timezone: Option<String>,
21    /// Scheduling mode; defaults to [`ScheduleKindDto::Cron`].
22    #[serde(default)]
23    pub schedule_kind: ScheduleKindDto,
24    /// Script parameters JSON; defaults to `{}`.
25    #[serde(default)]
26    pub params: Value,
27    /// Whether the scheduler should enqueue runs; defaults to `true`.
28    #[serde(default = "default_true")]
29    pub enabled: bool,
30    /// Max concurrent runs for this job; defaults to `1`.
31    #[serde(default = "default_concurrency")]
32    pub concurrency: i32,
33    /// Per-run timeout in milliseconds; `None` uses executor defaults.
34    pub timeout_ms: Option<i64>,
35    /// Actor/session JSON passed to [`ScriptContext`](chronon_core::ScriptContext); omitted fields are ignored.
36    #[serde(default)]
37    pub actor_json: Option<Value>,
38    /// Optional [`chronon_core::RetryPolicy`] JSON object.
39    #[serde(default)]
40    pub retry_policy: Option<Value>,
41    /// Optional [`chronon_core::MisfirePolicy`] JSON object.
42    #[serde(default)]
43    pub misfire_policy: Option<Value>,
44}
45
46fn default_true() -> bool {
47    true
48}
49
50fn default_concurrency() -> i32 {
51    1
52}
53
54/// Wire format for [`ScheduleKind`](chronon_core::ScheduleKind) in JSON (`snake_case`).
55#[derive(Debug, Default, Deserialize, Serialize, Clone, Copy)]
56#[serde(rename_all = "snake_case")]
57pub enum ScheduleKindDto {
58    /// Recurring cron schedule (default).
59    #[default]
60    Cron,
61    /// Fire once on next tick after upsert.
62    RunOnce,
63    /// Only runs triggered via API (`run_now`).
64    Manual,
65}
66
67impl From<ScheduleKindDto> for ScheduleKind {
68    fn from(dto: ScheduleKindDto) -> Self {
69        match dto {
70            ScheduleKindDto::Cron => Self::Cron,
71            ScheduleKindDto::RunOnce => Self::RunOnce,
72            ScheduleKindDto::Manual => Self::Manual,
73        }
74    }
75}
76
77impl From<ScheduleKind> for ScheduleKindDto {
78    fn from(kind: ScheduleKind) -> Self {
79        match kind {
80            ScheduleKind::Cron => Self::Cron,
81            ScheduleKind::RunOnce => Self::RunOnce,
82            ScheduleKind::Manual => Self::Manual,
83        }
84    }
85}
86
87/// Job summary returned by list/get/upsert endpoints.
88#[derive(Debug, Serialize, Deserialize)]
89pub struct JobResponse {
90    /// Stable job identifier (UUID).
91    pub job_id: String,
92    /// Human-readable name from upsert.
93    pub job_name: String,
94    /// Bound script name.
95    pub script_name: String,
96    /// Whether scheduling is active.
97    pub enabled: bool,
98    /// Current schedule mode.
99    pub schedule_kind: ScheduleKindDto,
100    /// Cron string when applicable.
101    pub cron_expr: Option<String>,
102    /// Timezone used for cron.
103    pub timezone: Option<String>,
104    /// Next scheduled fire time (RFC3339) when known.
105    pub next_run_at: Option<String>,
106    /// Monotonic revision counter bumped on material changes.
107    pub current_revision: i32,
108    /// Creation timestamp (RFC3339).
109    pub created_at: String,
110    /// Last upsert timestamp (RFC3339).
111    pub updated_at: String,
112}
113
114impl From<Job> for JobResponse {
115    fn from(job: Job) -> Self {
116        Self {
117            job_id: job.job_id,
118            job_name: job.job_name,
119            script_name: job.script_name,
120            enabled: job.enabled,
121            schedule_kind: job.schedule_kind.into(),
122            cron_expr: job.cron_expr,
123            timezone: job.timezone,
124            next_run_at: job.next_run_at.map(|t| t.to_rfc3339()),
125            current_revision: job.current_revision,
126            created_at: job.created_at.to_rfc3339(),
127            updated_at: job.updated_at.to_rfc3339(),
128        }
129    }
130}
131
132/// Body for job actions: pause, resume, and run-now.
133#[derive(Debug, Deserialize)]
134pub struct JobActionRequest {
135    /// Target job id (not job name).
136    pub job_id: String,
137    /// Optional params override for `run_now`; omitted uses the job's stored params.
138    #[serde(default)]
139    pub params: Option<Value>,
140}
141
142/// Run summary for list/get run endpoints.
143#[derive(Debug, Serialize, Deserialize)]
144pub struct RunResponse {
145    /// Unique run identifier.
146    pub run_id: String,
147    /// Parent job id when linked.
148    pub job_id: Option<String>,
149    /// Script executed for this run.
150    pub script_name: String,
151    /// Lowercase status string (`queued`, `running`, `success`, etc.).
152    pub status: String,
153    /// When the run was scheduled (RFC3339).
154    pub scheduled_for: String,
155    /// Execution start (RFC3339) when started.
156    pub started_at: Option<String>,
157    /// Completion time (RFC3339) when terminal.
158    pub finished_at: Option<String>,
159    /// Wall-clock duration in milliseconds when finished.
160    pub duration_ms: Option<i64>,
161    /// Attempt number for retries.
162    pub attempt: i32,
163}
164
165impl From<Run> for RunResponse {
166    fn from(run: Run) -> Self {
167        Self {
168            run_id: run.run_id,
169            job_id: run.job_id,
170            script_name: run.script_name,
171            status: run.status.to_string(),
172            scheduled_for: run.scheduled_for.to_rfc3339(),
173            started_at: run.started_at.map(|t| t.to_rfc3339()),
174            finished_at: run.finished_at.map(|t| t.to_rfc3339()),
175            duration_ms: run.duration_ms,
176            attempt: run.attempt,
177        }
178    }
179}
180
181/// Registered script metadata from `GET /scripts`.
182#[derive(Debug, Serialize, Deserialize)]
183pub struct ScriptResponse {
184    /// Script name used in job definitions.
185    pub name: String,
186    /// JSON description of handler parameters.
187    pub signature_json: String,
188    /// Stable hash of `signature_json` for change detection.
189    pub signature_hash: u64,
190}
191
192/// Query params for `GET /jobs`.
193#[derive(Debug, Default, Deserialize)]
194pub struct ListJobsQuery {
195    /// Exact job name match.
196    pub job_name: Option<String>,
197    /// Exact script name match.
198    pub script_name: Option<String>,
199    /// Filter by enabled flag (`true` / `false`).
200    pub enabled: Option<bool>,
201    /// Filter by schedule kind (`cron`, `run_once`, `manual`).
202    pub schedule_kind: Option<String>,
203    /// Pagination offset; defaults to 0.
204    pub offset: Option<usize>,
205    /// Page size; defaults to 100, capped at 1000.
206    pub limit: Option<usize>,
207}
208
209/// Query params for `GET /runs`.
210#[derive(Debug, Default, Deserialize)]
211pub struct ListRunsQuery {
212    /// Filter by parent job id.
213    pub job_id: Option<String>,
214    /// Filter by status string (case-insensitive).
215    pub status: Option<String>,
216    /// Pagination offset; defaults to 0 in handlers.
217    pub offset: Option<usize>,
218    /// Page size; defaults to 100, capped at [`chronon_core::MAX_LIST_LIMIT`] (1000).
219    pub limit: Option<usize>,
220}