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#[cfg(test)]
89mod tests {
90    use super::*;
91    use camel_api::component_metadata::ComponentMetadata;
92    use camel_component_log::LogComponent;
93    use camel_component_timer::TimerComponent;
94
95    #[test]
96    fn registry_starts_empty() {
97        let registry = Registry::new();
98        assert!(registry.is_empty());
99        assert_eq!(registry.len(), 0);
100        assert!(registry.get("timer").is_none());
101    }
102
103    #[test]
104    fn registry_registers_and_gets_components() {
105        let mut registry = Registry::new();
106        registry.register(Arc::new(TimerComponent::new()));
107        registry.register(Arc::new(LogComponent::new()));
108
109        assert_eq!(registry.len(), 2);
110        assert!(registry.get("timer").is_some());
111        assert!(registry.get("log").is_some());
112        assert!(!registry.is_empty());
113    }
114
115    #[test]
116    fn registry_get_or_err_reports_missing_component() {
117        let mut registry = Registry::new();
118        registry.register(Arc::new(TimerComponent::new()));
119
120        let err = match registry.get_or_err("missing") {
121            Ok(_) => panic!("must fail"),
122            Err(err) => err,
123        };
124        assert!(matches!(err, CamelError::ComponentNotFound(_)));
125    }
126
127    #[test]
128    fn registry_replaces_component_with_same_scheme() {
129        let mut registry = Registry::new();
130        registry.register(Arc::new(TimerComponent::new()));
131        registry.register(Arc::new(TimerComponent::new()));
132
133        assert_eq!(registry.len(), 1);
134        assert!(registry.get("timer").is_some());
135        assert_eq!(registry.all_metadata().len(), 1);
136    }
137
138    #[test]
139    fn registry_harvests_metadata_on_register() {
140        let mut registry = Registry::new();
141        registry.register(Arc::new(TimerComponent::new()));
142
143        let meta = registry.get_metadata("timer");
144        assert!(meta.is_some());
145        let meta = meta.unwrap(); // allow-unwrap
146        assert_eq!(meta.scheme, "timer");
147        assert_eq!(meta.schema_version, ComponentMetadata::SCHEMA_VERSION);
148    }
149
150    #[test]
151    fn registry_all_metadata_returns_all_schemes() {
152        let mut registry = Registry::new();
153        registry.register(Arc::new(TimerComponent::new()));
154        registry.register(Arc::new(LogComponent::new()));
155
156        let all = registry.all_metadata();
157        assert_eq!(all.len(), 2);
158    }
159
160    #[test]
161    fn registry_metadata_schemes_lists_all_keys() {
162        let mut registry = Registry::new();
163        registry.register(Arc::new(TimerComponent::new()));
164        registry.register(Arc::new(LogComponent::new()));
165
166        let mut schemes = registry.metadata_schemes();
167        schemes.sort();
168        assert_eq!(schemes, vec!["log".to_string(), "timer".to_string()]);
169    }
170}