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