camel_core/shared/components/domain/
registry.rs1use std::collections::HashMap;
2use std::sync::Arc;
3
4use camel_api::CamelError;
5use camel_api::component_metadata::ComponentMetadata;
6use camel_component_api::Component;
7
8pub struct Registry {
15 components: HashMap<String, Arc<dyn Component>>,
16 metadata: HashMap<String, ComponentMetadata>,
17}
18
19impl Registry {
20 pub fn new() -> Self {
22 Self {
23 components: HashMap::new(),
24 metadata: HashMap::new(),
25 }
26 }
27
28 pub fn register(&mut self, component: Arc<dyn Component>) {
35 let scheme = component.scheme().to_string();
36 let mut metadata = component.metadata();
37 if let Err(e) = metadata.validate_scheme(&scheme) {
38 tracing::warn!(scheme = %scheme, error = %e, "metadata scheme mismatch, normalizing");
39 metadata.scheme = scheme.clone();
40 }
41 self.metadata.insert(scheme.clone(), metadata);
42 self.components.insert(scheme, component);
43 }
44
45 pub fn get(&self, scheme: &str) -> Option<Arc<dyn Component>> {
47 self.components.get(scheme).cloned()
48 }
49
50 pub fn get_or_err(&self, scheme: &str) -> Result<Arc<dyn Component>, CamelError> {
52 self.get(scheme)
53 .ok_or_else(|| CamelError::ComponentNotFound(scheme.to_string()))
54 }
55
56 pub fn get_metadata(&self, scheme: &str) -> Option<ComponentMetadata> {
58 self.metadata.get(scheme).cloned()
59 }
60
61 pub fn all_metadata(&self) -> Vec<ComponentMetadata> {
63 self.metadata.values().cloned().collect()
64 }
65
66 pub fn metadata_schemes(&self) -> Vec<String> {
68 self.metadata.keys().cloned().collect()
69 }
70
71 pub fn len(&self) -> usize {
73 self.components.len()
74 }
75
76 pub fn is_empty(&self) -> bool {
78 self.components.is_empty()
79 }
80}
81
82impl Default for Registry {
83 fn default() -> Self {
84 Self::new()
85 }
86}
87
88pub struct RegistryComponentContext {
102 registry: Arc<std::sync::Mutex<Registry>>,
103 metrics: Arc<dyn camel_api::MetricsCollector>,
104 components_enabled: bool,
105}
106
107impl RegistryComponentContext {
108 pub fn new(
111 registry: Arc<std::sync::Mutex<Registry>>,
112 metrics: Option<Arc<dyn camel_api::MetricsCollector>>,
113 components_enabled: bool,
114 ) -> Self {
115 Self {
116 registry,
117 metrics: metrics.unwrap_or_else(|| Arc::new(camel_api::NoOpMetrics)),
118 components_enabled,
119 }
120 }
121}
122
123impl camel_component_api::ComponentContext for RegistryComponentContext {
124 fn resolve_component(&self, scheme: &str) -> Option<Arc<dyn camel_component_api::Component>> {
125 self.registry.lock().ok()?.get(scheme)
126 }
127
128 fn resolve_language(&self, _name: &str) -> Option<Arc<dyn camel_language_api::Language>> {
129 None
130 }
131
132 fn metrics(&self) -> Arc<dyn camel_api::MetricsCollector> {
133 self.metrics.clone()
134 }
135
136 fn component_metrics_enabled(&self) -> bool {
137 self.components_enabled
138 }
139
140 fn platform_service(&self) -> Arc<dyn camel_api::PlatformService> {
141 Arc::new(camel_api::NoopPlatformService::default())
142 }
143
144 fn register_route_health_check(
145 &self,
146 _route_id: &str,
147 _check: Arc<dyn camel_api::AsyncHealthCheck>,
148 ) {
149 }
150
151 fn unregister_route_health_check(&self, _route_id: &str) {}
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use std::time::Duration;
158
159 use camel_api::MetricsCollector;
160 use camel_api::component_metadata::ComponentMetadata;
161 use camel_component_api::{ComponentContext, RuntimeObservability};
162 use camel_component_log::LogComponent;
163 use camel_component_timer::TimerComponent;
164
165 struct RecordingState {
168 errors: Vec<(String, String)>,
169 component_ops: Vec<(String, String, String)>,
170 counters: Vec<(String, f64)>,
171 }
172
173 struct RecordingMetrics {
177 state: Arc<std::sync::Mutex<RecordingState>>,
178 }
179
180 impl RecordingMetrics {
181 fn new() -> Self {
182 Self {
183 state: Arc::new(std::sync::Mutex::new(RecordingState {
184 errors: Vec::new(),
185 component_ops: Vec::new(),
186 counters: Vec::new(),
187 })),
188 }
189 }
190
191 fn recorded_errors(&self) -> Vec<(String, String)> {
192 self.state
193 .lock()
194 .expect("recording state lock")
195 .errors
196 .clone()
197 }
198
199 fn recorded_component_operations(&self) -> Vec<(String, String, String)> {
200 self.state
201 .lock()
202 .expect("recording state lock")
203 .component_ops
204 .clone()
205 }
206
207 fn recorded_counters(&self) -> Vec<(String, f64)> {
208 self.state
209 .lock()
210 .expect("recording state lock")
211 .counters
212 .clone()
213 }
214 }
215
216 impl MetricsCollector for RecordingMetrics {
217 fn record_exchange_duration(&self, _route_id: &str, _duration: Duration) {}
218
219 fn increment_errors(&self, route_id: &str, error_type: &str) {
220 self.state
221 .lock()
222 .expect("recording state lock")
223 .errors
224 .push((route_id.to_string(), error_type.to_string()));
225 }
226
227 fn increment_exchanges(&self, _route_id: &str) {}
228
229 fn set_queue_depth(&self, _queue: &str, _depth: usize) {}
230
231 fn record_circuit_breaker_change(&self, _route_id: &str, _from: &str, _to: &str) {}
232
233 fn record_counter(&self, name: &str, value: f64, _labels: &[(&str, &str)]) {
234 self.state
235 .lock()
236 .expect("recording state lock")
237 .counters
238 .push((name.to_string(), value));
239 }
240
241 fn record_component_operation(&self, component: &str, operation: &str, outcome: &str) {
242 self.state
243 .lock()
244 .expect("recording state lock")
245 .component_ops
246 .push((
247 component.to_string(),
248 operation.to_string(),
249 outcome.to_string(),
250 ));
251 }
252 }
253
254 #[test]
255 fn registry_starts_empty() {
256 let registry = Registry::new();
257 assert!(registry.is_empty());
258 assert_eq!(registry.len(), 0);
259 assert!(registry.get("timer").is_none());
260 }
261
262 #[test]
263 fn registry_registers_and_gets_components() {
264 let mut registry = Registry::new();
265 registry.register(Arc::new(TimerComponent::new()));
266 registry.register(Arc::new(LogComponent::new()));
267
268 assert_eq!(registry.len(), 2);
269 assert!(registry.get("timer").is_some());
270 assert!(registry.get("log").is_some());
271 assert!(!registry.is_empty());
272 }
273
274 #[test]
275 fn registry_get_or_err_reports_missing_component() {
276 let mut registry = Registry::new();
277 registry.register(Arc::new(TimerComponent::new()));
278
279 let err = match registry.get_or_err("missing") {
280 Ok(_) => panic!("must fail"),
281 Err(err) => err,
282 };
283 assert!(matches!(err, CamelError::ComponentNotFound(_)));
284 }
285
286 #[test]
287 fn registry_replaces_component_with_same_scheme() {
288 let mut registry = Registry::new();
289 registry.register(Arc::new(TimerComponent::new()));
290 registry.register(Arc::new(TimerComponent::new()));
291
292 assert_eq!(registry.len(), 1);
293 assert!(registry.get("timer").is_some());
294 assert_eq!(registry.all_metadata().len(), 1);
295 }
296
297 #[test]
298 fn registry_harvests_metadata_on_register() {
299 let mut registry = Registry::new();
300 registry.register(Arc::new(TimerComponent::new()));
301
302 let meta = registry.get_metadata("timer");
303 assert!(meta.is_some());
304 let meta = meta.unwrap(); assert_eq!(meta.scheme, "timer");
306 assert_eq!(meta.schema_version, ComponentMetadata::SCHEMA_VERSION);
307 }
308
309 #[test]
310 fn registry_all_metadata_returns_all_schemes() {
311 let mut registry = Registry::new();
312 registry.register(Arc::new(TimerComponent::new()));
313 registry.register(Arc::new(LogComponent::new()));
314
315 let all = registry.all_metadata();
316 assert_eq!(all.len(), 2);
317 }
318
319 #[test]
320 fn registry_metadata_schemes_lists_all_keys() {
321 let mut registry = Registry::new();
322 registry.register(Arc::new(TimerComponent::new()));
323 registry.register(Arc::new(LogComponent::new()));
324
325 let mut schemes = registry.metadata_schemes();
326 schemes.sort();
327 assert_eq!(schemes, vec!["log".to_string(), "timer".to_string()]);
328 }
329
330 #[test]
331 fn metrics_returns_wired_collector_and_is_stable() {
332 let registry = Arc::new(std::sync::Mutex::new(Registry::new()));
333 let collector = Arc::new(RecordingMetrics::new());
334 let wired_dyn: Arc<dyn MetricsCollector> = collector.clone();
335 let ctx = RegistryComponentContext::new(registry, Some(collector), false);
336
337 let first = ComponentContext::metrics(&ctx);
338 let second = ComponentContext::metrics(&ctx);
339 assert!(Arc::ptr_eq(&first, &wired_dyn));
340 assert!(Arc::ptr_eq(&second, &wired_dyn));
341 }
342
343 #[test]
344 fn component_metrics_enabled_reflects_constructor_lever() {
345 let on = RegistryComponentContext::new(
346 Arc::new(std::sync::Mutex::new(Registry::new())),
347 None,
348 true,
349 );
350 let off = RegistryComponentContext::new(
351 Arc::new(std::sync::Mutex::new(Registry::new())),
352 None,
353 false,
354 );
355
356 assert!(ComponentContext::component_metrics_enabled(&on));
357 assert!(!ComponentContext::component_metrics_enabled(&off));
358 }
359
360 #[test]
361 fn facade_error_family_reaches_wired_collector_with_lever_off() {
362 let registry = Arc::new(std::sync::Mutex::new(Registry::new()));
363 let collector = Arc::new(RecordingMetrics::new());
364 let ctx = RegistryComponentContext::new(registry, Some(collector.clone()), false);
365
366 let facade = RuntimeObservability::component_metrics(&ctx);
367 facade.observe("wasm", "invoke", true);
368
369 assert_eq!(
370 collector.recorded_errors(),
371 vec![("wasm".to_string(), "e:wasm:invoke".to_string())]
372 );
373 }
374
375 #[test]
376 fn facade_component_family_gated_by_lever() {
377 let registry = Arc::new(std::sync::Mutex::new(Registry::new()));
378 let collector = Arc::new(RecordingMetrics::new());
379 let ctx_on = RegistryComponentContext::new(registry.clone(), Some(collector.clone()), true);
380 let ctx_off = RegistryComponentContext::new(registry, Some(collector.clone()), false);
381
382 RuntimeObservability::component_metrics(&ctx_on).observe("wasm", "invoke", false);
383 RuntimeObservability::component_metrics(&ctx_off).observe("wasm", "invoke", false);
384
385 assert_eq!(
386 collector.recorded_component_operations(),
387 vec![(
388 "wasm".to_string(),
389 "invoke".to_string(),
390 "success".to_string()
391 )]
392 );
393 assert!(collector.recorded_errors().is_empty());
394 assert!(collector.recorded_counters().is_empty());
395 }
396
397 #[test]
398 fn late_registered_collector_reaches_registry_context() {
399 let registry = Arc::new(std::sync::Mutex::new(Registry::new()));
400 let handle = Arc::new(camel_api::MetricsHandle::new());
401 let handle_dyn: Arc<dyn MetricsCollector> = handle.clone();
402 let recording = Arc::new(RecordingMetrics::new());
403 let ctx = RegistryComponentContext::new(registry, Some(handle_dyn), false);
404
405 handle.register(recording.clone());
406 ComponentContext::metrics(&ctx).increment_errors("wasm", "e:wasm:invoke");
407
408 assert_eq!(
409 recording.recorded_errors(),
410 vec![("wasm".to_string(), "e:wasm:invoke".to_string())]
411 );
412 }
413
414 #[test]
415 fn none_falls_back_to_noop_semantics() {
416 let registry = Arc::new(std::sync::Mutex::new(Registry::new()));
417 let ctx = RegistryComponentContext::new(registry, None, false);
418
419 ComponentContext::metrics(&ctx);
423 RuntimeObservability::component_metrics(&ctx).observe("wasm", "invoke", true);
424
425 assert!(!ComponentContext::component_metrics_enabled(&ctx));
426 }
427
428 #[test]
429 fn resolve_component_unaffected_by_observability_params() {
430 let mut registry = Registry::new();
431 registry.register(Arc::new(TimerComponent::new()));
432 let registry = Arc::new(std::sync::Mutex::new(registry));
433 let ctx = RegistryComponentContext::new(registry, None, false);
434
435 assert!(ctx.resolve_component("timer").is_some());
436 }
437}