Skip to main content

modo/cron/
context.rs

1use std::sync::Arc;
2
3use crate::error::{Error, Result};
4use crate::extractor::Service;
5use crate::service::RegistrySnapshot;
6
7use super::meta::Meta;
8
9/// Execution context passed to every cron handler invocation.
10///
11/// Carries a snapshot of the service registry and the job metadata for the
12/// current tick. This type is constructed by the scheduler and passed to
13/// [`CronHandler::call`] — handlers do not create it directly. Use
14/// [`FromCronContext`] to extract individual values from the context as
15/// handler arguments.
16pub struct CronContext {
17    pub(crate) registry: Arc<RegistrySnapshot>,
18    pub(crate) meta: Meta,
19}
20
21/// Extracts a value from a [`CronContext`].
22///
23/// Implement this trait to make a type usable as a cron handler argument.
24/// Built-in implementations are provided for [`Service<T>`] and [`Meta`].
25pub trait FromCronContext: Sized {
26    /// Attempt to extract `Self` from the given context.
27    ///
28    /// # Errors
29    ///
30    /// Returns an error if the required data is not present (e.g. a service
31    /// was not registered before the scheduler was built).
32    fn from_cron_context(ctx: &CronContext) -> Result<Self>;
33}
34
35impl<T: Send + Sync + 'static> FromCronContext for Service<T> {
36    fn from_cron_context(ctx: &CronContext) -> Result<Self> {
37        ctx.registry.get::<T>().map(Service).ok_or_else(|| {
38            Error::internal(format!(
39                "service not found in registry: {}",
40                std::any::type_name::<T>()
41            ))
42        })
43    }
44}
45
46impl FromCronContext for Meta {
47    fn from_cron_context(ctx: &CronContext) -> Result<Self> {
48        Ok(ctx.meta.clone())
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use std::any::{Any, TypeId};
56    use std::collections::HashMap;
57
58    fn test_context() -> CronContext {
59        let mut services: HashMap<TypeId, Arc<dyn Any + Send + Sync>> = HashMap::new();
60        services.insert(TypeId::of::<u32>(), Arc::new(42u32));
61        let snapshot = Arc::new(RegistrySnapshot::new(services));
62
63        CronContext {
64            registry: snapshot,
65            meta: Meta {
66                name: "test_job".to_string(),
67                deadline: None,
68                tick: chrono::Utc::now(),
69            },
70        }
71    }
72
73    #[test]
74    fn service_extractor_finds_registered() {
75        let ctx = test_context();
76        let svc = Service::<u32>::from_cron_context(&ctx).unwrap();
77        assert_eq!(*svc.0, 42);
78    }
79
80    #[test]
81    fn service_extractor_fails_for_missing() {
82        let ctx = test_context();
83        let result = Service::<String>::from_cron_context(&ctx);
84        assert!(result.is_err());
85    }
86
87    #[test]
88    fn meta_extractor_returns_meta() {
89        let ctx = test_context();
90        let meta = Meta::from_cron_context(&ctx).unwrap();
91        assert_eq!(meta.name, "test_job");
92    }
93}