Skip to main content

chartml_core/
registry.rs

1use std::collections::HashMap;
2use crate::plugin::{DataSource, TransformMiddleware, ChartRenderer, DatasourceResolver};
3
4/// Central plugin registry. Holds all registered plugins and provides lookup.
5pub struct ChartMLRegistry {
6    data_sources: HashMap<String, Box<dyn DataSource>>,
7    transforms: Vec<Box<dyn TransformMiddleware>>,
8    renderers: HashMap<String, Box<dyn ChartRenderer>>,
9    datasource_resolver: Option<Box<dyn DatasourceResolver>>,
10}
11
12impl ChartMLRegistry {
13    pub fn new() -> Self {
14        Self {
15            data_sources: HashMap::new(),
16            transforms: Vec::new(),
17            renderers: HashMap::new(),
18            datasource_resolver: None,
19        }
20    }
21
22    pub fn register_data_source(&mut self, name: &str, source: impl DataSource + 'static) {
23        self.data_sources.insert(name.to_string(), Box::new(source));
24    }
25
26    pub fn register_transform(&mut self, middleware: impl TransformMiddleware + 'static) {
27        self.transforms.push(Box::new(middleware));
28    }
29
30    pub fn set_transform(&mut self, middleware: impl TransformMiddleware + 'static) {
31        self.transforms.clear();
32        self.transforms.push(Box::new(middleware));
33    }
34
35    pub fn register_renderer(&mut self, chart_type: &str, renderer: impl ChartRenderer + 'static) {
36        self.renderers.insert(chart_type.to_string(), Box::new(renderer));
37    }
38
39    pub fn set_datasource_resolver(&mut self, resolver: impl DatasourceResolver + 'static) {
40        self.datasource_resolver = Some(Box::new(resolver));
41    }
42
43    pub fn get_renderer(&self, chart_type: &str) -> Option<&dyn ChartRenderer> {
44        self.renderers.get(chart_type).map(|r| r.as_ref())
45    }
46
47    pub fn get_data_source(&self, name: &str) -> Option<&dyn DataSource> {
48        self.data_sources.get(name).map(|s| s.as_ref())
49    }
50
51    /// Get the first registered transform middleware (if any).
52    pub fn get_transform(&self) -> Option<&dyn TransformMiddleware> {
53        self.transforms.first().map(|t| t.as_ref())
54    }
55
56    pub fn has_renderer(&self, chart_type: &str) -> bool {
57        self.renderers.contains_key(chart_type)
58    }
59
60    pub fn renderer_types(&self) -> Vec<String> {
61        self.renderers.keys().cloned().collect()
62    }
63}
64
65impl Default for ChartMLRegistry {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    #![allow(clippy::unwrap_used)]
74    use super::*;
75    use crate::data::DataTable;
76    use crate::element::{ChartElement, ViewBox};
77    use crate::error::ChartError;
78    use crate::plugin::ChartConfig;
79
80    // Create a mock renderer for testing
81    struct MockRenderer;
82
83    impl ChartRenderer for MockRenderer {
84        fn render(&self, _data: &DataTable, _config: &ChartConfig) -> Result<ChartElement, ChartError> {
85            Ok(ChartElement::Svg {
86                viewbox: ViewBox::new(0.0, 0.0, 800.0, 400.0),
87                width: Some(800.0),
88                height: Some(400.0),
89                class: "mock".to_string(),
90                children: vec![],
91            })
92        }
93    }
94
95    #[test]
96    fn registry_register_and_lookup_renderer() {
97        let mut registry = ChartMLRegistry::new();
98        registry.register_renderer("bar", MockRenderer);
99        assert!(registry.has_renderer("bar"));
100        assert!(!registry.has_renderer("pie"));
101    }
102
103    #[test]
104    fn registry_renderer_types() {
105        let mut registry = ChartMLRegistry::new();
106        registry.register_renderer("bar", MockRenderer);
107        registry.register_renderer("line", MockRenderer);
108        let types = registry.renderer_types();
109        assert!(types.contains(&"bar".to_string()));
110        assert!(types.contains(&"line".to_string()));
111    }
112
113    #[test]
114    fn registry_default() {
115        let registry = ChartMLRegistry::default();
116        assert!(registry.renderer_types().is_empty());
117    }
118}