runledger_runtime/
registry.rs1use 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}