nautilus_rs/resources/clockwork/mod.rs
1pub mod types;
2
3use std::sync::Arc;
4
5use crate::{error::Error, http::HttpClient};
6use types::{
7 CreateCronJobParams, CreateDelayedJobParams, CronJob, DelayedJob, Execution,
8 UpdateCronJobParams,
9};
10
11/// Clockwork service client — Cron-as-a-Service.
12///
13/// Schedules recurring cron jobs and one-off delayed jobs that invoke your HTTP
14/// endpoints, and reports on their execution history.
15///
16/// Obtain a `Clockwork` instance either as part of the unified [`Verne`] client
17/// or standalone:
18///
19/// ```no_run
20/// // Standalone
21/// use nautilus_rs::Clockwork;
22/// let clockwork = Clockwork::new("vrn_clockwork_live_sk_…");
23///
24/// // Via unified client
25/// use nautilus_rs::Verne;
26/// # fn run() -> Result<(), nautilus_rs::Error> {
27/// let verne = Verne::builder().clockwork("vrn_clockwork_live_sk_…").build()?;
28/// let clockwork = verne.clockwork()?;
29/// # Ok(())
30/// # }
31/// ```
32///
33/// [`Verne`]: crate::Verne
34pub struct Clockwork {
35 http: Arc<HttpClient>,
36}
37
38impl std::fmt::Debug for Clockwork {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("Clockwork").finish_non_exhaustive()
41 }
42}
43
44impl Clockwork {
45 /// Create a `Clockwork` client with default settings.
46 ///
47 /// Panics if the API key is empty or the HTTP client cannot be
48 /// initialised. Use [`Clockwork::builder`] for fallible construction.
49 pub fn new(api_key: impl Into<String>) -> Self {
50 Self::builder()
51 .api_key(api_key)
52 .build()
53 .expect("failed to build Clockwork client")
54 }
55
56 /// Return a [`ClockworkBuilder`] for fine-grained configuration.
57 pub fn builder() -> ClockworkBuilder {
58 ClockworkBuilder::default()
59 }
60
61 pub(crate) fn from_http(http: Arc<HttpClient>) -> Self {
62 Self { http }
63 }
64
65 /// Return a [`CronJobsClient`] for managing recurring cron jobs.
66 pub fn jobs(&self) -> CronJobsClient {
67 CronJobsClient {
68 http: Arc::clone(&self.http),
69 }
70 }
71
72 /// Return a [`DelayedJobsClient`] for managing one-off delayed jobs.
73 pub fn delayed(&self) -> DelayedJobsClient {
74 DelayedJobsClient {
75 http: Arc::clone(&self.http),
76 }
77 }
78}
79
80/// Builder for a standalone [`Clockwork`] client.
81///
82/// # Example
83///
84/// ```no_run
85/// use nautilus_rs::Clockwork;
86///
87/// let clockwork = Clockwork::builder()
88/// .api_key("vrn_clockwork_live_sk_…")
89/// .timeout_secs(15)
90/// .build()
91/// .expect("invalid configuration");
92/// ```
93#[derive(Default)]
94pub struct ClockworkBuilder {
95 api_key: Option<String>,
96 base_url: Option<String>,
97 timeout_secs: Option<u64>,
98}
99
100impl ClockworkBuilder {
101 /// Set the Clockwork API key (**required**).
102 pub fn api_key(mut self, key: impl Into<String>) -> Self {
103 self.api_key = Some(key.into());
104 self
105 }
106
107 /// Override the API base URL (default: `https://api.vernesoft.com`).
108 pub fn base_url(mut self, url: impl Into<String>) -> Self {
109 self.base_url = Some(url.into());
110 self
111 }
112
113 /// Set the HTTP request timeout in seconds (default: `30`).
114 pub fn timeout_secs(mut self, secs: u64) -> Self {
115 self.timeout_secs = Some(secs);
116 self
117 }
118
119 /// Consume the builder and return a configured [`Clockwork`].
120 ///
121 /// # Errors
122 ///
123 /// Returns [`Error::Config`] if the API key was not set.
124 pub fn build(self) -> Result<Clockwork, Error> {
125 let key = self
126 .api_key
127 .ok_or_else(|| Error::Config("clockwork API key is required".into()))?;
128 let http = HttpClient::new(key, self.base_url, self.timeout_secs)?;
129 Ok(Clockwork {
130 http: Arc::new(http),
131 })
132 }
133}
134
135/// Access to the `/v1/clockwork/jobs` endpoints.
136///
137/// Obtain via [`Clockwork::jobs`].
138pub struct CronJobsClient {
139 http: Arc<HttpClient>,
140}
141
142impl CronJobsClient {
143 /// List all cron jobs.
144 ///
145 /// Maps to `GET /v1/clockwork/jobs`.
146 ///
147 /// # Example
148 ///
149 /// ```no_run
150 /// use nautilus_rs::Clockwork;
151 ///
152 /// # async fn run() -> Result<(), nautilus_rs::Error> {
153 /// let clockwork = Clockwork::new("vrn_clockwork_live_sk_…");
154 /// for job in clockwork.jobs().list().await? {
155 /// println!("{} — {}", job.name, job.schedule);
156 /// }
157 /// # Ok(())
158 /// # }
159 /// ```
160 pub async fn list(&self) -> Result<Vec<CronJob>, Error> {
161 self.http.get("/v1/clockwork/jobs").await
162 }
163
164 /// Create a new cron job.
165 ///
166 /// Maps to `POST /v1/clockwork/jobs`.
167 ///
168 /// # Example
169 ///
170 /// ```no_run
171 /// use nautilus_rs::{Clockwork, CreateCronJobParams};
172 ///
173 /// # async fn run() -> Result<(), nautilus_rs::Error> {
174 /// let clockwork = Clockwork::new("vrn_clockwork_live_sk_…");
175 /// let job = clockwork.jobs().create(CreateCronJobParams {
176 /// name: "nightly-report".into(),
177 /// schedule: "0 2 * * *".into(),
178 /// url: "https://example.com/hooks/report".into(),
179 /// ..Default::default()
180 /// }).await?;
181 /// println!("created job {}", job.id);
182 /// # Ok(())
183 /// # }
184 /// ```
185 pub async fn create(&self, params: CreateCronJobParams) -> Result<CronJob, Error> {
186 self.http.post("/v1/clockwork/jobs", ¶ms, false).await
187 }
188
189 /// Partially update an existing cron job.
190 ///
191 /// Only the fields set on `params` are sent; the rest are left unchanged.
192 /// Maps to `PATCH /v1/clockwork/jobs/{id}`.
193 pub async fn update(
194 &self,
195 job_id: &str,
196 params: UpdateCronJobParams,
197 ) -> Result<CronJob, Error> {
198 self.http
199 .patch(&format!("/v1/clockwork/jobs/{job_id}"), ¶ms)
200 .await
201 }
202
203 /// Permanently delete a cron job.
204 ///
205 /// Maps to `DELETE /v1/clockwork/jobs/{id}`.
206 pub async fn delete(&self, job_id: &str) -> Result<(), Error> {
207 self.http
208 .delete(&format!("/v1/clockwork/jobs/{job_id}"))
209 .await
210 }
211
212 /// List the execution history for a cron job.
213 ///
214 /// Maps to `GET /v1/clockwork/jobs/{id}/executions`.
215 pub async fn executions(&self, job_id: &str) -> Result<Vec<Execution>, Error> {
216 self.http
217 .get(&format!("/v1/clockwork/jobs/{job_id}/executions"))
218 .await
219 }
220}
221
222/// Access to the `/v1/clockwork/delayed` endpoints.
223///
224/// Obtain via [`Clockwork::delayed`].
225pub struct DelayedJobsClient {
226 http: Arc<HttpClient>,
227}
228
229impl DelayedJobsClient {
230 /// List all delayed jobs.
231 ///
232 /// Maps to `GET /v1/clockwork/delayed`.
233 ///
234 /// # Example
235 ///
236 /// ```no_run
237 /// use nautilus_rs::Clockwork;
238 ///
239 /// # async fn run() -> Result<(), nautilus_rs::Error> {
240 /// let clockwork = Clockwork::new("vrn_clockwork_live_sk_…");
241 /// for job in clockwork.delayed().list().await? {
242 /// println!("{} runs at {}", job.name, job.run_at);
243 /// }
244 /// # Ok(())
245 /// # }
246 /// ```
247 pub async fn list(&self) -> Result<Vec<DelayedJob>, Error> {
248 self.http.get("/v1/clockwork/delayed").await
249 }
250
251 /// Schedule a new one-off delayed job.
252 ///
253 /// Maps to `POST /v1/clockwork/delayed`.
254 ///
255 /// # Example
256 ///
257 /// ```no_run
258 /// use nautilus_rs::{Clockwork, CreateDelayedJobParams};
259 ///
260 /// # async fn run() -> Result<(), nautilus_rs::Error> {
261 /// let clockwork = Clockwork::new("vrn_clockwork_live_sk_…");
262 /// let job = clockwork.delayed().create(CreateDelayedJobParams {
263 /// name: "send-reminder".into(),
264 /// run_at: "2026-01-01T12:00:00Z".into(),
265 /// url: "https://example.com/hooks/reminder".into(),
266 /// ..Default::default()
267 /// }).await?;
268 /// println!("scheduled job {}", job.id);
269 /// # Ok(())
270 /// # }
271 /// ```
272 pub async fn create(&self, params: CreateDelayedJobParams) -> Result<DelayedJob, Error> {
273 self.http
274 .post("/v1/clockwork/delayed", ¶ms, false)
275 .await
276 }
277
278 /// Cancel a pending delayed job.
279 ///
280 /// Maps to `DELETE /v1/clockwork/delayed/{id}`.
281 pub async fn cancel(&self, job_id: &str) -> Result<(), Error> {
282 self.http
283 .delete(&format!("/v1/clockwork/delayed/{job_id}"))
284 .await
285 }
286
287 /// List the execution history for a delayed job.
288 ///
289 /// Maps to `GET /v1/clockwork/delayed/{id}/executions`.
290 pub async fn executions(&self, job_id: &str) -> Result<Vec<Execution>, Error> {
291 self.http
292 .get(&format!("/v1/clockwork/delayed/{job_id}/executions"))
293 .await
294 }
295}