Skip to main content

camel_core/
template.rs

1use std::collections::HashMap;
2use std::sync::Mutex;
3
4use camel_api::{CamelError, RouteTemplateSpec, TemplateError, TemplateInstanceRecord};
5
6/// Thread-safe registry for route templates and their instantiated records.
7///
8/// The registry stores template specifications (`RouteTemplateSpec`) keyed by
9/// their unique ID, and tracks `TemplateInstanceRecord` entries for each
10/// template that has been instantiated at runtime.
11pub struct TemplateRegistry {
12    templates: Mutex<HashMap<String, RouteTemplateSpec>>,
13    instances: Mutex<HashMap<String, Vec<TemplateInstanceRecord>>>,
14}
15
16impl TemplateRegistry {
17    /// Create a new empty `TemplateRegistry`.
18    pub fn new() -> Self {
19        Self {
20            templates: Mutex::new(HashMap::new()),
21            instances: Mutex::new(HashMap::new()),
22        }
23    }
24
25    /// Register a route template specification.
26    ///
27    /// Returns `Err(CamelError)` if a template with the same ID is already registered.
28    pub fn register(&self, spec: RouteTemplateSpec) -> Result<(), CamelError> {
29        let id = spec.id.clone();
30        let mut templates = self
31            .templates
32            .lock()
33            .expect("template registry mutex poisoned"); // allow-unwrap
34        if templates.contains_key(&id) {
35            return Err(TemplateError::AlreadyRegistered(id).into());
36        }
37        templates.insert(id, spec);
38        Ok(())
39    }
40
41    /// Retrieve a template specification by its ID.
42    pub fn get(&self, id: &str) -> Option<RouteTemplateSpec> {
43        let templates = self
44            .templates
45            .lock()
46            .expect("template registry mutex poisoned"); // allow-unwrap
47        templates.get(id).cloned()
48    }
49
50    /// Return all registered template IDs.
51    pub fn template_ids(&self) -> Vec<String> {
52        let templates = self
53            .templates
54            .lock()
55            .expect("template registry mutex poisoned"); // allow-unwrap
56        templates.keys().cloned().collect()
57    }
58
59    /// Record a newly instantiated template instance.
60    pub fn record_instance(&self, record: TemplateInstanceRecord) {
61        let template_id = record.template_id.clone();
62        let mut instances = self
63            .instances
64            .lock()
65            .expect("template instances mutex poisoned"); // allow-unwrap
66        instances.entry(template_id).or_default().push(record);
67    }
68
69    /// Return all instance records for a given template ID.
70    pub fn instances(&self, template_id: &str) -> Vec<TemplateInstanceRecord> {
71        let instances = self
72            .instances
73            .lock()
74            .expect("template instances mutex poisoned"); // allow-unwrap
75        instances.get(template_id).cloned().unwrap_or_default()
76    }
77}
78
79impl Default for TemplateRegistry {
80    fn default() -> Self {
81        Self::new()
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use std::collections::BTreeMap;
89    use uuid::Uuid;
90
91    fn make_template(id: &str) -> RouteTemplateSpec {
92        RouteTemplateSpec {
93            id: id.to_string(),
94            parameters: vec![],
95            route: serde_json::json!({"from": {"uri": "timer:tick"}}),
96        }
97    }
98
99    fn make_instance(template_id: &str, route_id: &str) -> TemplateInstanceRecord {
100        TemplateInstanceRecord {
101            template_id: template_id.to_string(),
102            instance_id: Uuid::nil(),
103            route_id: route_id.to_string(),
104            parameters: BTreeMap::new(),
105        }
106    }
107
108    #[test]
109    fn register_and_get_template() {
110        let registry = TemplateRegistry::new();
111        let spec = make_template("test-tpl");
112        registry.register(spec.clone()).unwrap();
113
114        let retrieved = registry.get("test-tpl").expect("template should exist");
115        assert_eq!(retrieved.id, "test-tpl");
116    }
117
118    #[test]
119    fn get_returns_none_for_unknown_template() {
120        let registry = TemplateRegistry::new();
121        assert!(registry.get("nonexistent").is_none());
122    }
123
124    #[test]
125    fn duplicate_registration_returns_error() {
126        let registry = TemplateRegistry::new();
127        registry.register(make_template("dup")).unwrap();
128        let result = registry.register(make_template("dup"));
129        assert!(result.is_err());
130        let err = result.unwrap_err();
131        assert!(format!("{err}").contains("already registered"));
132    }
133
134    #[test]
135    fn template_ids_returns_all_registered_ids() {
136        let registry = TemplateRegistry::new();
137        registry.register(make_template("a")).unwrap();
138        registry.register(make_template("b")).unwrap();
139        registry.register(make_template("c")).unwrap();
140
141        let mut ids = registry.template_ids();
142        ids.sort();
143        assert_eq!(ids, vec!["a", "b", "c"]);
144    }
145
146    #[test]
147    fn record_and_retrieve_instances() {
148        let registry = TemplateRegistry::new();
149        let inst1 = make_instance("tpl-1", "route-1");
150        let inst2 = make_instance("tpl-1", "route-2");
151        let inst3 = make_instance("tpl-2", "route-3");
152
153        registry.record_instance(inst1);
154        registry.record_instance(inst2);
155        registry.record_instance(inst3);
156
157        let tpl1_instances = registry.instances("tpl-1");
158        assert_eq!(tpl1_instances.len(), 2);
159
160        let tpl2_instances = registry.instances("tpl-2");
161        assert_eq!(tpl2_instances.len(), 1);
162        assert_eq!(tpl2_instances[0].route_id, "route-3");
163    }
164
165    #[test]
166    fn instances_returns_empty_for_unknown_template() {
167        let registry = TemplateRegistry::new();
168        let instances = registry.instances("nonexistent");
169        assert!(instances.is_empty());
170    }
171
172    #[test]
173    fn template_ids_empty_when_no_templates() {
174        let registry = TemplateRegistry::new();
175        assert!(registry.template_ids().is_empty());
176    }
177}