camel_core/
component_metadata_catalog.rs1use std::sync::{Arc, Mutex};
8
9use camel_api::component_metadata::{ComponentMetadata, ComponentMetadataCatalog};
10
11use crate::shared::components::domain::Registry;
12
13pub struct RuntimeComponentMetadataCatalog {
16 registry: Arc<Mutex<Registry>>,
17}
18
19impl RuntimeComponentMetadataCatalog {
20 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") .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") .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") .register(Arc::new(TimerComponent::new()));
60
61 let catalog = RuntimeComponentMetadataCatalog::new(Arc::clone(®istry));
62
63 let meta = catalog.get_metadata("timer");
64 assert!(meta.is_some());
65 assert_eq!(meta.unwrap().scheme, "timer"); 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") .register(Arc::new(TimerComponent::new()));
77
78 let catalog = RuntimeComponentMetadataCatalog::new(Arc::clone(®istry));
79
80 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"); 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(®istry));
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"); 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(®istry));
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"); 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(®istry));
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 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"); 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(®istry));
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}