Skip to main content

camel_core/shared/components/domain/
registry.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use camel_api::CamelError;
5use camel_api::component_metadata::ComponentMetadata;
6use camel_component_api::Component;
7
8/// Registry that stores components by their URI scheme.
9///
10/// Also harvests and indexes [`ComponentMetadata`] for each registered
11/// component, so the metadata can be queried through a
12/// [`ComponentMetadataCatalog`](camel_api::component_metadata::ComponentMetadataCatalog)
13/// without re-invoking the component.
14pub struct Registry {
15    components: HashMap<String, Arc<dyn Component>>,
16    metadata: HashMap<String, ComponentMetadata>,
17}
18
19impl Registry {
20    /// Create an empty registry.
21    pub fn new() -> Self {
22        Self {
23            components: HashMap::new(),
24            metadata: HashMap::new(),
25        }
26    }
27
28    /// Register a component. Replaces any existing component with the same scheme.
29    ///
30    /// Harvests the component's [`ComponentMetadata`] and indexes it by scheme
31    /// in parallel with the component insertion. Validates that the metadata's
32    /// scheme matches the component's scheme, normalizing on mismatch with a
33    /// warning log.
34    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    /// Look up a component by scheme.
46    pub fn get(&self, scheme: &str) -> Option<Arc<dyn Component>> {
47        self.components.get(scheme).cloned()
48    }
49
50    /// Look up a component by scheme, returning an error if not found.
51    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    /// Look up harvested metadata for a component by scheme.
57    pub fn get_metadata(&self, scheme: &str) -> Option<ComponentMetadata> {
58        self.metadata.get(scheme).cloned()
59    }
60
61    /// Return metadata for every registered component.
62    pub fn all_metadata(&self) -> Vec<ComponentMetadata> {
63        self.metadata.values().cloned().collect()
64    }
65
66    /// Return the schemes of every registered component's metadata.
67    pub fn metadata_schemes(&self) -> Vec<String> {
68        self.metadata.keys().cloned().collect()
69    }
70
71    /// Returns the number of registered components.
72    pub fn len(&self) -> usize {
73        self.components.len()
74    }
75
76    /// Returns true if no components are registered.
77    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
88/// Adapter that lets `Registry` participate as a `ComponentContext`.
89///
90/// Wraps the shared `Arc<Mutex<Registry>>` and delegates `resolve_component`
91/// to `Registry::get`. All other `ComponentContext` methods are no-ops — wasm
92/// only needs component lookups.
93pub struct RegistryComponentContext {
94    registry: Arc<std::sync::Mutex<Registry>>,
95}
96
97impl RegistryComponentContext {
98    pub fn new(registry: Arc<std::sync::Mutex<Registry>>) -> Self {
99        Self { registry }
100    }
101}
102
103impl camel_component_api::ComponentContext for RegistryComponentContext {
104    fn resolve_component(&self, scheme: &str) -> Option<Arc<dyn camel_component_api::Component>> {
105        self.registry.lock().ok()?.get(scheme)
106    }
107
108    fn resolve_language(&self, _name: &str) -> Option<Arc<dyn camel_language_api::Language>> {
109        None
110    }
111
112    fn metrics(&self) -> Arc<dyn camel_api::MetricsCollector> {
113        Arc::new(camel_api::NoOpMetrics)
114    }
115
116    fn platform_service(&self) -> Arc<dyn camel_api::PlatformService> {
117        Arc::new(camel_api::NoopPlatformService::default())
118    }
119
120    fn register_route_health_check(
121        &self,
122        _route_id: &str,
123        _check: Arc<dyn camel_api::AsyncHealthCheck>,
124    ) {
125    }
126
127    fn unregister_route_health_check(&self, _route_id: &str) {}
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use camel_api::component_metadata::ComponentMetadata;
134    use camel_component_log::LogComponent;
135    use camel_component_timer::TimerComponent;
136
137    #[test]
138    fn registry_starts_empty() {
139        let registry = Registry::new();
140        assert!(registry.is_empty());
141        assert_eq!(registry.len(), 0);
142        assert!(registry.get("timer").is_none());
143    }
144
145    #[test]
146    fn registry_registers_and_gets_components() {
147        let mut registry = Registry::new();
148        registry.register(Arc::new(TimerComponent::new()));
149        registry.register(Arc::new(LogComponent::new()));
150
151        assert_eq!(registry.len(), 2);
152        assert!(registry.get("timer").is_some());
153        assert!(registry.get("log").is_some());
154        assert!(!registry.is_empty());
155    }
156
157    #[test]
158    fn registry_get_or_err_reports_missing_component() {
159        let mut registry = Registry::new();
160        registry.register(Arc::new(TimerComponent::new()));
161
162        let err = match registry.get_or_err("missing") {
163            Ok(_) => panic!("must fail"),
164            Err(err) => err,
165        };
166        assert!(matches!(err, CamelError::ComponentNotFound(_)));
167    }
168
169    #[test]
170    fn registry_replaces_component_with_same_scheme() {
171        let mut registry = Registry::new();
172        registry.register(Arc::new(TimerComponent::new()));
173        registry.register(Arc::new(TimerComponent::new()));
174
175        assert_eq!(registry.len(), 1);
176        assert!(registry.get("timer").is_some());
177        assert_eq!(registry.all_metadata().len(), 1);
178    }
179
180    #[test]
181    fn registry_harvests_metadata_on_register() {
182        let mut registry = Registry::new();
183        registry.register(Arc::new(TimerComponent::new()));
184
185        let meta = registry.get_metadata("timer");
186        assert!(meta.is_some());
187        let meta = meta.unwrap(); // allow-unwrap
188        assert_eq!(meta.scheme, "timer");
189        assert_eq!(meta.schema_version, ComponentMetadata::SCHEMA_VERSION);
190    }
191
192    #[test]
193    fn registry_all_metadata_returns_all_schemes() {
194        let mut registry = Registry::new();
195        registry.register(Arc::new(TimerComponent::new()));
196        registry.register(Arc::new(LogComponent::new()));
197
198        let all = registry.all_metadata();
199        assert_eq!(all.len(), 2);
200    }
201
202    #[test]
203    fn registry_metadata_schemes_lists_all_keys() {
204        let mut registry = Registry::new();
205        registry.register(Arc::new(TimerComponent::new()));
206        registry.register(Arc::new(LogComponent::new()));
207
208        let mut schemes = registry.metadata_schemes();
209        schemes.sort();
210        assert_eq!(schemes, vec!["log".to_string(), "timer".to_string()]);
211    }
212}