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::CamelError;
51 use camel_api::component_metadata::{
52 CapabilityQuery, ComponentMetadataCatalog, OptionKind, UriOption,
53 };
54 use camel_component_api::{Component, ComponentContext, Endpoint};
55 use camel_component_timer::TimerComponent;
56
57 struct StubComponent {
66 scheme: String,
67 metadata: ComponentMetadata,
68 }
69
70 impl StubComponent {
71 fn new(scheme: &str, with_options: Vec<&str>) -> Self {
72 let uri_options: Vec<_> = with_options
73 .into_iter()
74 .map(|name| UriOption::new(name, "stub option", OptionKind::String))
75 .collect();
76 let metadata = ComponentMetadata::minimal(scheme)
77 .with_description("stub component for catalog tests")
78 .with_uri_options(uri_options);
79 Self {
80 scheme: scheme.to_string(),
81 metadata,
82 }
83 }
84 }
85
86 impl Component for StubComponent {
87 fn scheme(&self) -> &str {
88 &self.scheme
89 }
90
91 fn metadata(&self) -> ComponentMetadata {
92 self.metadata.clone()
93 }
94
95 fn create_endpoint(
96 &self,
97 _uri: &str,
98 _ctx: &dyn ComponentContext,
99 ) -> Result<Box<dyn Endpoint>, CamelError> {
100 Err(CamelError::EndpointCreationFailed(
101 "StubComponent: create_endpoint not implemented".to_string(),
102 ))
103 }
104 }
105
106 #[test]
107 fn catalog_exposes_registered_metadata() {
108 let registry = Arc::new(Mutex::new(Registry::new()));
109 registry
110 .lock()
111 .expect("mutex poisoned: another thread panicked while holding this lock") .register(Arc::new(TimerComponent::new()));
113
114 let catalog = RuntimeComponentMetadataCatalog::new(Arc::clone(®istry));
115
116 let meta = catalog.get_metadata("timer");
117 assert!(meta.is_some());
118 assert_eq!(meta.unwrap().scheme, "timer"); assert_eq!(catalog.schemes(), vec!["timer".to_string()]);
120 assert_eq!(catalog.all_metadata().len(), 1);
121 }
122
123 #[test]
124 fn catalog_query_capabilities_default_impl() {
125 let registry = Arc::new(Mutex::new(Registry::new()));
126 registry
127 .lock()
128 .expect("mutex poisoned: another thread panicked while holding this lock") .register(Arc::new(TimerComponent::new()));
130
131 let catalog = RuntimeComponentMetadataCatalog::new(Arc::clone(®istry));
132
133 let results = catalog.query_capabilities(&CapabilityQuery::default());
135 assert_eq!(results.len(), 1);
136 }
137
138 #[test]
139 fn all_components_in_catalog() {
140 use camel_component_container::ContainerComponent;
141 use camel_component_cron::CronComponent;
142 use camel_component_direct::DirectComponent;
143 use camel_component_file::FileComponent;
144 use camel_component_log::LogComponent;
145 use camel_component_mock::MockComponent;
146 use camel_component_opensearch::OpenSearchComponent;
147 use camel_component_seda::SedaComponent;
148 use camel_component_sql::SqlComponent;
149
150 let registry = Arc::new(Mutex::new(Registry::new()));
151 {
152 let mut reg = registry
153 .lock()
154 .expect("mutex poisoned: another thread panicked while holding this lock"); reg.register(Arc::new(SqlComponent::new()));
156 reg.register(Arc::new(FileComponent::new()));
157 reg.register(Arc::new(CronComponent::new()));
158 reg.register(Arc::new(OpenSearchComponent::new()));
159 reg.register(Arc::new(StubComponent::new("ws", vec!["opt"])));
160 reg.register(Arc::new(ContainerComponent::new()));
161 reg.register(Arc::new(TimerComponent::new()));
162 reg.register(Arc::new(DirectComponent::new()));
163 reg.register(Arc::new(SedaComponent::new()));
164 reg.register(Arc::new(LogComponent::new()));
165 reg.register(Arc::new(MockComponent::new()));
166 reg.register(Arc::new(StubComponent::new("http", vec!["opt"])));
167 }
168
169 let catalog = RuntimeComponentMetadataCatalog::new(Arc::clone(®istry));
170
171 let expected_schemes: &[&str] = &[
172 "sql",
173 "file",
174 "cron",
175 "opensearch",
176 "ws",
177 "container",
178 "timer",
179 "direct",
180 "seda",
181 "log",
182 "mock",
183 "http",
184 ];
185
186 for scheme in expected_schemes {
187 let meta = catalog
188 .get_metadata(scheme)
189 .unwrap_or_else(|| panic!("missing metadata for scheme '{scheme}'"));
190 if *scheme != "mock" {
192 assert!(
193 !meta.uri_options.is_empty(),
194 "uri_options must be non-empty for scheme '{scheme}'"
195 );
196 }
197 assert!(
198 !meta.scheme.is_empty(),
199 "scheme must be non-empty for '{scheme}'"
200 );
201 assert!(
202 !meta.description.is_empty(),
203 "description must be non-empty for scheme '{scheme}'"
204 );
205 }
206 }
207
208 #[test]
209 fn no_duplicate_option_names_all() {
210 use camel_component_container::ContainerComponent;
211 use camel_component_cron::CronComponent;
212 use camel_component_direct::DirectComponent;
213 use camel_component_file::FileComponent;
214 use camel_component_log::LogComponent;
215 use camel_component_mock::MockComponent;
216 use camel_component_opensearch::OpenSearchComponent;
217 use camel_component_seda::SedaComponent;
218 use camel_component_sql::SqlComponent;
219
220 let registry = Arc::new(Mutex::new(Registry::new()));
221 {
222 let mut reg = registry
223 .lock()
224 .expect("mutex poisoned: another thread panicked while holding this lock"); reg.register(Arc::new(SqlComponent::new()));
226 reg.register(Arc::new(FileComponent::new()));
227 reg.register(Arc::new(CronComponent::new()));
228 reg.register(Arc::new(OpenSearchComponent::new()));
229 reg.register(Arc::new(StubComponent::new("ws", vec!["opt"])));
230 reg.register(Arc::new(ContainerComponent::new()));
231 reg.register(Arc::new(TimerComponent::new()));
232 reg.register(Arc::new(DirectComponent::new()));
233 reg.register(Arc::new(SedaComponent::new()));
234 reg.register(Arc::new(LogComponent::new()));
235 reg.register(Arc::new(MockComponent::new()));
236 reg.register(Arc::new(StubComponent::new("http", vec!["opt"])));
237 }
238
239 let catalog = RuntimeComponentMetadataCatalog::new(Arc::clone(®istry));
240
241 let schemes = &[
242 "sql",
243 "file",
244 "cron",
245 "opensearch",
246 "ws",
247 "container",
248 "timer",
249 "direct",
250 "seda",
251 "log",
252 "mock",
253 "http",
254 ];
255
256 for scheme in schemes {
257 let meta = catalog
258 .get_metadata(scheme)
259 .unwrap_or_else(|| panic!("missing metadata for scheme '{scheme}'"));
260 let mut names: Vec<&str> = meta.uri_options.iter().map(|o| o.name.as_str()).collect();
261 let original_len = names.len();
262 names.sort_unstable();
263 names.dedup();
264 assert_eq!(
265 names.len(),
266 original_len,
267 "duplicate option names found in scheme '{scheme}'"
268 );
269 }
270 }
271}