Skip to main content

runledger_runtime/
registry.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4pub use runledger_core::jobs::JobHandler;
5use runledger_core::jobs::{JobHandlerRegistry, JobType};
6
7#[derive(Default, Clone)]
8pub struct JobRegistry {
9    handlers: HashMap<JobType<'static>, Arc<dyn JobHandler>>,
10}
11
12impl JobRegistry {
13    #[must_use]
14    pub fn new() -> Self {
15        Self::default()
16    }
17
18    pub fn register<H>(&mut self, handler: H)
19    where
20        H: JobHandler + 'static,
21    {
22        self.register_boxed(Arc::new(handler));
23    }
24
25    #[must_use]
26    pub fn get(&self, job_type: JobType<'_>) -> Option<Arc<dyn JobHandler>> {
27        self.handlers.get(job_type.as_str()).cloned()
28    }
29
30    #[must_use]
31    pub fn registered_types(&self) -> Vec<JobType<'_>> {
32        let mut keys: Vec<JobType<'_>> = self.handlers.keys().copied().collect();
33        keys.sort_unstable();
34        keys
35    }
36}
37
38impl JobHandlerRegistry for JobRegistry {
39    fn register_boxed(&mut self, handler: Arc<dyn JobHandler>) {
40        self.handlers.insert(handler.job_type(), handler);
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use async_trait::async_trait;
48    use runledger_core::jobs::{JobContext, JobFailure, JobType};
49    use serde_json::Value;
50    use serde_json::json;
51    use uuid::Uuid;
52
53    struct ExampleHandler;
54
55    #[async_trait]
56    impl JobHandler for ExampleHandler {
57        fn job_type(&self) -> JobType<'static> {
58            JobType::new("jobs.example")
59        }
60
61        async fn execute(&self, _context: JobContext, _payload: Value) -> Result<(), JobFailure> {
62            Ok(())
63        }
64    }
65
66    fn test_context() -> JobContext {
67        JobContext {
68            job_id: Uuid::now_v7(),
69            run_number: 1,
70            attempt: 1,
71            organization_id: None,
72            worker_id: "registry-test-worker".to_string(),
73        }
74    }
75
76    #[tokio::test]
77    async fn registered_handler_executes_successfully_via_trait_object() {
78        let mut registry = JobRegistry::new();
79        registry.register(ExampleHandler);
80        let handler = registry
81            .get(JobType::new("jobs.example"))
82            .expect("handler exists");
83
84        handler
85            .execute(test_context(), json!({}))
86            .await
87            .expect("registered handler should execute");
88    }
89
90    #[test]
91    fn registered_types_returns_sorted_job_types() {
92        let mut registry = JobRegistry::new();
93        registry.register(ExampleHandler);
94
95        assert_eq!(
96            registry.registered_types(),
97            vec![JobType::new("jobs.example")]
98        );
99    }
100}