Skip to main content

modo/cron/
context.rs

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