Skip to main content

chronon_core/
handle.rs

1//! Typed script handle for job scheduling.
2
3use std::marker::PhantomData;
4
5use serde::Serialize;
6
7use crate::models::Job;
8use crate::Result;
9
10/// A typed handle for scheduling a script with specific parameters.
11///
12/// Created by the `#[chronon::script]` macro. The attribute turns the annotated
13/// function into a handle factory (`fn nightly_cleanup() -> ScriptHandle<…>`) and
14/// moves the body to an internal `__*_impl` entry point used by the executor.
15///
16/// After building a [`Job`], set [`crate::ScheduleKind`] / cron fields and upsert via
17/// `CoordinatorService` (Mode 1–2) or `RemoteCoordinatorClient` (Mode 3).
18///
19/// # Examples
20///
21/// Build a default [`Job`] from the macro-generated handle, then upsert it:
22///
23/// ```
24/// use chronon_core::{Job, ScriptHandle};
25/// use serde::Serialize;
26///
27/// #[derive(Serialize)]
28/// struct NightlyCleanupParams {
29///     retention_days: u32,
30/// }
31///
32/// let handle = ScriptHandle::<NightlyCleanupParams>::new("nightly_cleanup");
33/// let job: Job = handle
34///     .job_with_params(
35///         "nightly-job",
36///         &NightlyCleanupParams {
37///             retention_days: 7,
38///         },
39///     )
40///     .expect("params serialize");
41/// assert_eq!(job.script_name, "nightly_cleanup");
42/// assert_eq!(job.params_json["retention_days"], 7);
43/// ```
44///
45/// Runnable end-to-end sample: `cargo run -p uf-chronon --example script_handle_job --features mem`.
46#[derive(Debug, Clone)]
47pub struct ScriptHandle<P> {
48    name: &'static str,
49    _params: PhantomData<P>,
50}
51
52impl<P> ScriptHandle<P> {
53    /// Create a new script handle (typically called by macro-generated code).
54    pub const fn new(name: &'static str) -> Self {
55        Self {
56            name,
57            _params: PhantomData,
58        }
59    }
60
61    /// Stable script registry name.
62    pub const fn name(&self) -> &'static str {
63        self.name
64    }
65
66    /// Baseline [`Job`] pointing at this script (`Job::new` defaults).
67    ///
68    /// Populate schedule fields (`schedule_kind`, `cron_expr`, `next_run_at`, …)
69    /// before upserting via the coordinator service or HTTP API.
70    pub fn job(&self, job_name: impl Into<String>) -> Job {
71        Job::new(job_name, self.name)
72    }
73}
74
75impl<P: Serialize> ScriptHandle<P> {
76    /// Baseline [`Job`] with typed params serialized into `params_json`.
77    pub fn job_with_params(&self, job_name: impl Into<String>, params: &P) -> Result<Job> {
78        let mut job = self.job(job_name);
79        job.params_json = serde_json::to_value(params).map_err(crate::ChrononError::from)?;
80        Ok(job)
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use serde::Serialize;
88
89    #[derive(Serialize)]
90    struct DemoParams {
91        n: u32,
92    }
93
94    #[test]
95    fn job_sets_script_name() {
96        let handle = ScriptHandle::<()>::new("demo");
97        let job = handle.job("demo-job");
98        assert_eq!(job.job_name, "demo-job");
99        assert_eq!(job.script_name, "demo");
100    }
101
102    #[test]
103    fn job_with_params_serializes() {
104        let handle = ScriptHandle::<DemoParams>::new("demo");
105        let job = handle
106            .job_with_params("demo-job", &DemoParams { n: 3 })
107            .unwrap();
108        assert_eq!(job.params_json["n"], 3);
109    }
110}