Skip to main content

camel_component_api/
cron_service.rs

1//! CronService SPI — trait and types for cron-triggered scheduling backends.
2//!
3//! The default implementation is `TokioCronService` in `camel-cron`.
4//! Future backends (e.g. a persistent Quartz-style scheduler) implement
5//! this trait from `component-api` without depending on `camel-cron`.
6
7use std::future::Future;
8use std::pin::Pin;
9use std::sync::Arc;
10
11use chrono::{DateTime, Utc};
12use chrono_tz::Tz;
13use tokio_util::sync::CancellationToken;
14
15use crate::CamelError;
16
17/// A scheduled cron trigger handed to a [`CronService`].
18#[derive(Debug, Clone)]
19pub struct CronSchedule {
20    /// Normalized 6-field cron expression in `cron` crate format
21    /// (`sec min hour dom month dow`). The `CronConsumer` normalizes
22    /// the 5-field Unix input from the URI by prepending `0` for seconds.
23    pub expression: String,
24    /// IANA timezone for evaluating the expression.
25    pub time_zone: Tz,
26    /// Stable trigger identity — the `name` from the URI path.
27    pub trigger_id: String,
28    /// Route owning this schedule, if known at Endpoint creation.
29    pub route_id: Option<String>,
30}
31
32/// Metadata about a single cron fire, passed to [`CronCallback`].
33#[derive(Debug, Clone)]
34pub struct CronFire {
35    /// When the schedule computed this fire should occur.
36    pub scheduled_at: DateTime<Utc>,
37    /// When the service actually invoked the callback.
38    pub fired_at: DateTime<Utc>,
39    /// 1-based fire counter.
40    pub counter: u64,
41}
42
43/// Async, fallible callback invoked on each cron fire.
44///
45/// The `CronConsumer` builds this from its `ConsumerContext` to submit an
46/// Exchange to the pipeline. Returning `Err` propagates the failure through
47/// `CronService::run` -> `Consumer::start` to Route supervision (ADR-0007).
48pub type CronCallback = Arc<
49    dyn Fn(CronFire) -> Pin<Box<dyn Future<Output = Result<(), CamelError>> + Send>> + Send + Sync,
50>;
51
52/// SPI for cron-triggered scheduling backends.
53///
54/// The default implementation is `TokioCronService` (in `camel-cron`).
55#[async_trait::async_trait]
56pub trait CronService: Send + Sync {
57    /// Run the scheduling loop until `cancel` is triggered or `callback` fails.
58    ///
59    /// - Clean cancellation -> return `Ok(())`.
60    /// - Callback failure -> return `Err(...)` for ADR-0007 supervision.
61    async fn run(
62        &self,
63        schedule: &CronSchedule,
64        callback: CronCallback,
65        cancel: CancellationToken,
66    ) -> Result<(), CamelError>;
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn cron_schedule_construction() {
75        let schedule = CronSchedule {
76            // Normalized 6-field format (sec min hour dom month dow)
77            expression: "0 0 2 * * *".to_string(),
78            time_zone: Tz::UTC,
79            trigger_id: "nightly".to_string(),
80            route_id: Some("route-1".to_string()),
81        };
82        assert_eq!(schedule.expression, "0 0 2 * * *");
83        assert_eq!(schedule.trigger_id, "nightly");
84        assert_eq!(schedule.route_id.as_deref(), Some("route-1"));
85    }
86
87    #[test]
88    fn cron_fire_construction() {
89        let now = Utc::now();
90        let fire = CronFire {
91            scheduled_at: now,
92            fired_at: now,
93            counter: 1,
94        };
95        assert_eq!(fire.counter, 1);
96    }
97}