Skip to main content

camel_core/
component_metadata_catalog.rs

1//! Runtime implementation of [`ComponentMetadataCatalog`].
2//!
3//! Thin wrapper around the component [`Registry`]'s `Arc<Mutex<Registry>>`
4//! that implements the query trait. Created on-demand via
5//! [`CamelContext::metadata_catalog`](crate::context::CamelContext::metadata_catalog).
6
7use std::sync::{Arc, Mutex};
8
9use camel_api::component_metadata::{ComponentMetadata, ComponentMetadataCatalog};
10
11use crate::shared::components::domain::Registry;
12
13/// Runtime catalog of component metadata backed by the live component
14/// [`Registry`].
15pub struct RuntimeComponentMetadataCatalog {
16    registry: Arc<Mutex<Registry>>,
17}
18
19impl RuntimeComponentMetadataCatalog {
20    /// Wrap an existing `Arc<Mutex<Registry>>` to expose it as a
21    /// [`ComponentMetadataCatalog`].
22    pub fn new(registry: Arc<Mutex<Registry>>) -> Self {
23        Self { registry }
24    }
25}
26
27impl ComponentMetadataCatalog for RuntimeComponentMetadataCatalog {
28    fn get_metadata(&self, scheme: &str) -> Option<ComponentMetadata> {
29        self.registry.lock().ok()?.get_metadata(scheme)
30    }
31
32    fn schemes(&self) -> Vec<String> {
33        self.registry
34            .lock()
35            .expect("mutex poisoned: another thread panicked while holding this lock") // allow-unwrap
36            .metadata_schemes()
37    }
38
39    fn all_metadata(&self) -> Vec<ComponentMetadata> {
40        self.registry
41            .lock()
42            .expect("mutex poisoned: another thread panicked while holding this lock") // allow-unwrap
43            .all_metadata()
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50    use camel_api::component_metadata::{CapabilityQuery, ComponentMetadataCatalog};
51    use camel_component_timer::TimerComponent;
52
53    #[test]
54    fn catalog_exposes_registered_metadata() {
55        let registry = Arc::new(Mutex::new(Registry::new()));
56        registry
57            .lock()
58            .expect("mutex poisoned: another thread panicked while holding this lock") // allow-unwrap
59            .register(Arc::new(TimerComponent::new()));
60
61        let catalog = RuntimeComponentMetadataCatalog::new(Arc::clone(&registry));
62
63        let meta = catalog.get_metadata("timer");
64        assert!(meta.is_some());
65        assert_eq!(meta.unwrap().scheme, "timer"); // allow-unwrap
66        assert_eq!(catalog.schemes(), vec!["timer".to_string()]);
67        assert_eq!(catalog.all_metadata().len(), 1);
68    }
69
70    #[test]
71    fn catalog_query_capabilities_default_impl() {
72        let registry = Arc::new(Mutex::new(Registry::new()));
73        registry
74            .lock()
75            .expect("mutex poisoned: another thread panicked while holding this lock") // allow-unwrap
76            .register(Arc::new(TimerComponent::new()));
77
78        let catalog = RuntimeComponentMetadataCatalog::new(Arc::clone(&registry));
79
80        // No constraints => all metadata returned via the trait default impl.
81        let results = catalog.query_capabilities(&CapabilityQuery::default());
82        assert_eq!(results.len(), 1);
83    }
84
85    #[test]
86    fn all_phase2_schemes_have_options() {
87        use camel_component_container::ContainerComponent;
88        use camel_component_cron::CronComponent;
89        use camel_component_file::FileComponent;
90        use camel_component_opensearch::OpenSearchComponent;
91        use camel_component_sql::SqlComponent;
92        use camel_component_ws::WsComponent;
93
94        let registry = Arc::new(Mutex::new(Registry::new()));
95        {
96            let mut reg = registry
97                .lock()
98                .expect("mutex poisoned: another thread panicked while holding this lock"); // allow-unwrap
99            reg.register(Arc::new(SqlComponent::new()));
100            reg.register(Arc::new(FileComponent::new()));
101            reg.register(Arc::new(CronComponent::new()));
102            reg.register(Arc::new(OpenSearchComponent::new()));
103            reg.register(Arc::new(WsComponent::new()));
104            reg.register(Arc::new(ContainerComponent::new()));
105            reg.register(Arc::new(TimerComponent::new()));
106        }
107
108        let catalog = RuntimeComponentMetadataCatalog::new(Arc::clone(&registry));
109
110        let schemes = &[
111            "sql",
112            "file",
113            "cron",
114            "opensearch",
115            "ws",
116            "container",
117            "timer",
118        ];
119
120        for scheme in schemes {
121            let meta = catalog
122                .get_metadata(scheme)
123                .unwrap_or_else(|| panic!("missing metadata for scheme '{scheme}'"));
124            assert!(
125                !meta.uri_options.is_empty(),
126                "uri_options must be non-empty for scheme '{scheme}'"
127            );
128        }
129    }
130
131    #[test]
132    fn no_duplicate_option_names() {
133        use camel_component_container::ContainerComponent;
134        use camel_component_cron::CronComponent;
135        use camel_component_file::FileComponent;
136        use camel_component_opensearch::OpenSearchComponent;
137        use camel_component_sql::SqlComponent;
138        use camel_component_ws::WsComponent;
139
140        let registry = Arc::new(Mutex::new(Registry::new()));
141        {
142            let mut reg = registry
143                .lock()
144                .expect("mutex poisoned: another thread panicked while holding this lock"); // allow-unwrap
145            reg.register(Arc::new(SqlComponent::new()));
146            reg.register(Arc::new(FileComponent::new()));
147            reg.register(Arc::new(CronComponent::new()));
148            reg.register(Arc::new(OpenSearchComponent::new()));
149            reg.register(Arc::new(WsComponent::new()));
150            reg.register(Arc::new(ContainerComponent::new()));
151            reg.register(Arc::new(TimerComponent::new()));
152        }
153
154        let catalog = RuntimeComponentMetadataCatalog::new(Arc::clone(&registry));
155
156        let schemes = &[
157            "sql",
158            "file",
159            "cron",
160            "opensearch",
161            "ws",
162            "container",
163            "timer",
164        ];
165
166        for scheme in schemes {
167            let meta = catalog
168                .get_metadata(scheme)
169                .unwrap_or_else(|| panic!("missing metadata for scheme '{scheme}'"));
170            let mut names: Vec<&str> = meta.uri_options.iter().map(|o| o.name.as_str()).collect();
171            let original_len = names.len();
172            names.sort_unstable();
173            names.dedup();
174            assert_eq!(
175                names.len(),
176                original_len,
177                "duplicate option names found in scheme '{scheme}'"
178            );
179        }
180    }
181
182    #[test]
183    fn all_components_in_catalog() {
184        use camel_component_container::ContainerComponent;
185        use camel_component_cron::CronComponent;
186        use camel_component_direct::DirectComponent;
187        use camel_component_file::FileComponent;
188        use camel_component_http::HttpComponent;
189        use camel_component_log::LogComponent;
190        use camel_component_mock::MockComponent;
191        use camel_component_opensearch::OpenSearchComponent;
192        use camel_component_seda::SedaComponent;
193        use camel_component_sql::SqlComponent;
194        use camel_component_ws::WsComponent;
195
196        let registry = Arc::new(Mutex::new(Registry::new()));
197        {
198            let mut reg = registry
199                .lock()
200                .expect("mutex poisoned: another thread panicked while holding this lock"); // allow-unwrap
201            reg.register(Arc::new(SqlComponent::new()));
202            reg.register(Arc::new(FileComponent::new()));
203            reg.register(Arc::new(CronComponent::new()));
204            reg.register(Arc::new(OpenSearchComponent::new()));
205            reg.register(Arc::new(WsComponent::new()));
206            reg.register(Arc::new(ContainerComponent::new()));
207            reg.register(Arc::new(TimerComponent::new()));
208            reg.register(Arc::new(DirectComponent::new()));
209            reg.register(Arc::new(SedaComponent::new()));
210            reg.register(Arc::new(LogComponent::new()));
211            reg.register(Arc::new(MockComponent::new()));
212            reg.register(Arc::new(HttpComponent::new()));
213        }
214
215        let catalog = RuntimeComponentMetadataCatalog::new(Arc::clone(&registry));
216
217        let expected_schemes: &[&str] = &[
218            "sql",
219            "file",
220            "cron",
221            "opensearch",
222            "ws",
223            "container",
224            "timer",
225            "direct",
226            "seda",
227            "log",
228            "mock",
229            "http",
230        ];
231
232        for scheme in expected_schemes {
233            let meta = catalog
234                .get_metadata(scheme)
235                .unwrap_or_else(|| panic!("missing metadata for scheme '{scheme}'"));
236            // mock may have empty options; others should be non-empty
237            if *scheme != "mock" {
238                assert!(
239                    !meta.uri_options.is_empty(),
240                    "uri_options must be non-empty for scheme '{scheme}'"
241                );
242            }
243            assert!(
244                !meta.scheme.is_empty(),
245                "scheme must be non-empty for '{scheme}'"
246            );
247            assert!(
248                !meta.description.is_empty(),
249                "description must be non-empty for scheme '{scheme}'"
250            );
251        }
252    }
253
254    #[test]
255    fn no_duplicate_option_names_all() {
256        use camel_component_container::ContainerComponent;
257        use camel_component_cron::CronComponent;
258        use camel_component_direct::DirectComponent;
259        use camel_component_file::FileComponent;
260        use camel_component_http::HttpComponent;
261        use camel_component_log::LogComponent;
262        use camel_component_mock::MockComponent;
263        use camel_component_opensearch::OpenSearchComponent;
264        use camel_component_seda::SedaComponent;
265        use camel_component_sql::SqlComponent;
266        use camel_component_ws::WsComponent;
267
268        let registry = Arc::new(Mutex::new(Registry::new()));
269        {
270            let mut reg = registry
271                .lock()
272                .expect("mutex poisoned: another thread panicked while holding this lock"); // allow-unwrap
273            reg.register(Arc::new(SqlComponent::new()));
274            reg.register(Arc::new(FileComponent::new()));
275            reg.register(Arc::new(CronComponent::new()));
276            reg.register(Arc::new(OpenSearchComponent::new()));
277            reg.register(Arc::new(WsComponent::new()));
278            reg.register(Arc::new(ContainerComponent::new()));
279            reg.register(Arc::new(TimerComponent::new()));
280            reg.register(Arc::new(DirectComponent::new()));
281            reg.register(Arc::new(SedaComponent::new()));
282            reg.register(Arc::new(LogComponent::new()));
283            reg.register(Arc::new(MockComponent::new()));
284            reg.register(Arc::new(HttpComponent::new()));
285        }
286
287        let catalog = RuntimeComponentMetadataCatalog::new(Arc::clone(&registry));
288
289        let schemes = &[
290            "sql",
291            "file",
292            "cron",
293            "opensearch",
294            "ws",
295            "container",
296            "timer",
297            "direct",
298            "seda",
299            "log",
300            "mock",
301            "http",
302        ];
303
304        for scheme in schemes {
305            let meta = catalog
306                .get_metadata(scheme)
307                .unwrap_or_else(|| panic!("missing metadata for scheme '{scheme}'"));
308            let mut names: Vec<&str> = meta.uri_options.iter().map(|o| o.name.as_str()).collect();
309            let original_len = names.len();
310            names.sort_unstable();
311            names.dedup();
312            assert_eq!(
313                names.len(),
314                original_len,
315                "duplicate option names found in scheme '{scheme}'"
316            );
317        }
318    }
319}