Skip to main content

mabi_core/
factory.rs

1//! Device factory and plugin system for extensibility.
2//!
3//! This module provides abstractions for creating devices dynamically
4//! and registering protocol plugins at runtime.
5
6use std::collections::HashMap;
7use std::sync::Arc;
8
9use parking_lot::RwLock;
10
11use crate::config::DeviceConfig;
12use crate::device::BoxedDevice;
13use crate::error::{Error, Result};
14use crate::protocol::Protocol;
15use crate::version::RELEASE_VERSION;
16
17/// A factory trait for creating devices.
18///
19/// Implement this trait to add support for new protocols or device types.
20///
21/// # Example
22///
23/// ```ignore
24/// struct ModbusDeviceFactory;
25///
26/// impl DeviceFactory for ModbusDeviceFactory {
27///     fn protocol(&self) -> Protocol {
28///         Protocol::ModbusTcp
29///     }
30///
31///     fn create(&self, config: DeviceConfig) -> Result<BoxedDevice> {
32///         Ok(Box::new(ModbusDevice::new(config)?))
33///     }
34/// }
35/// ```
36pub trait DeviceFactory: Send + Sync {
37    /// Get the protocol this factory handles.
38    fn protocol(&self) -> Protocol;
39
40    /// Create a device from configuration.
41    fn create(&self, config: DeviceConfig) -> Result<BoxedDevice>;
42
43    /// Create multiple devices from configurations.
44    fn create_batch(&self, configs: Vec<DeviceConfig>) -> Result<Vec<BoxedDevice>> {
45        configs.into_iter().map(|c| self.create(c)).collect()
46    }
47
48    /// Validate device configuration without creating the device.
49    fn validate(&self, config: &DeviceConfig) -> Result<()> {
50        if config.id.is_empty() {
51            return Err(Error::Config("Device ID cannot be empty".into()));
52        }
53        if config.name.is_empty() {
54            return Err(Error::Config("Device name cannot be empty".into()));
55        }
56        if config.protocol != self.protocol() {
57            return Err(Error::Config(format!(
58                "Protocol mismatch: expected {:?}, got {:?}",
59                self.protocol(),
60                config.protocol
61            )));
62        }
63        Ok(())
64    }
65
66    /// Get factory metadata.
67    fn metadata(&self) -> FactoryMetadata {
68        FactoryMetadata {
69            protocol: self.protocol(),
70            version: RELEASE_VERSION.to_string(),
71            description: format!("{:?} device factory", self.protocol()),
72            capabilities: Vec::new(),
73        }
74    }
75}
76
77/// Metadata about a device factory.
78#[derive(Debug, Clone)]
79pub struct FactoryMetadata {
80    /// Protocol this factory handles.
81    pub protocol: Protocol,
82    /// Factory version.
83    pub version: String,
84    /// Description.
85    pub description: String,
86    /// Supported capabilities.
87    pub capabilities: Vec<String>,
88}
89
90/// A type-erased device factory.
91pub type BoxedFactory = Box<dyn DeviceFactory>;
92
93/// Registry for device factories.
94///
95/// This allows registering factories for different protocols at runtime,
96/// enabling a plugin-like architecture.
97///
98/// # Example
99///
100/// ```ignore
101/// let mut registry = FactoryRegistry::new();
102/// registry.register(ModbusDeviceFactory)?;
103///
104/// let config = DeviceConfig { /* ... */ };
105/// let device = registry.create_device(config)?;
106/// ```
107pub struct FactoryRegistry {
108    factories: RwLock<HashMap<Protocol, Arc<BoxedFactory>>>,
109}
110
111impl FactoryRegistry {
112    /// Create a new empty registry.
113    pub fn new() -> Self {
114        Self {
115            factories: RwLock::new(HashMap::new()),
116        }
117    }
118
119    /// Register a device factory.
120    pub fn register<F: DeviceFactory + 'static>(&self, factory: F) -> Result<()> {
121        let protocol = factory.protocol();
122        let mut factories = self.factories.write();
123
124        if factories.contains_key(&protocol) {
125            return Err(Error::Config(format!(
126                "Factory already registered for protocol {:?}",
127                protocol
128            )));
129        }
130
131        factories.insert(protocol, Arc::new(Box::new(factory)));
132        Ok(())
133    }
134
135    /// Register a boxed factory.
136    pub fn register_boxed(&self, factory: BoxedFactory) -> Result<()> {
137        let protocol = factory.protocol();
138        let mut factories = self.factories.write();
139
140        if factories.contains_key(&protocol) {
141            return Err(Error::Config(format!(
142                "Factory already registered for protocol {:?}",
143                protocol
144            )));
145        }
146
147        factories.insert(protocol, Arc::new(factory));
148        Ok(())
149    }
150
151    /// Unregister a factory for a protocol.
152    pub fn unregister(&self, protocol: Protocol) -> bool {
153        self.factories.write().remove(&protocol).is_some()
154    }
155
156    /// Get a factory for a protocol.
157    pub fn get(&self, protocol: Protocol) -> Option<Arc<BoxedFactory>> {
158        self.factories.read().get(&protocol).cloned()
159    }
160
161    /// Check if a factory is registered for a protocol.
162    pub fn has(&self, protocol: Protocol) -> bool {
163        self.factories.read().contains_key(&protocol)
164    }
165
166    /// Get all registered protocols.
167    pub fn protocols(&self) -> Vec<Protocol> {
168        self.factories.read().keys().copied().collect()
169    }
170
171    /// Get metadata for all registered factories.
172    pub fn all_metadata(&self) -> Vec<FactoryMetadata> {
173        self.factories
174            .read()
175            .values()
176            .map(|f| f.metadata())
177            .collect()
178    }
179
180    /// Create a device using the appropriate factory.
181    pub fn create_device(&self, config: DeviceConfig) -> Result<BoxedDevice> {
182        let factory = self
183            .get(config.protocol)
184            .ok_or_else(|| Error::NotSupported(format!("Protocol {:?}", config.protocol)))?;
185
186        factory.validate(&config)?;
187        factory.create(config)
188    }
189
190    /// Create multiple devices.
191    pub fn create_devices(&self, configs: Vec<DeviceConfig>) -> Result<Vec<BoxedDevice>> {
192        configs.into_iter().map(|c| self.create_device(c)).collect()
193    }
194}
195
196impl Default for FactoryRegistry {
197    fn default() -> Self {
198        Self::new()
199    }
200}
201
202/// Plugin trait for extending the simulator.
203///
204/// Plugins can register factories, add event handlers, and customize behavior.
205pub trait Plugin: Send + Sync {
206    /// Get plugin name.
207    fn name(&self) -> &str;
208
209    /// Get plugin version.
210    fn version(&self) -> &str {
211        RELEASE_VERSION
212    }
213
214    /// Get plugin description.
215    fn description(&self) -> &str {
216        ""
217    }
218
219    /// Initialize the plugin (called when loaded).
220    fn initialize(&mut self) -> Result<()> {
221        Ok(())
222    }
223
224    /// Register device factories with the registry.
225    fn register_factories(&self, _registry: &FactoryRegistry) -> Result<()> {
226        Ok(())
227    }
228
229    /// Shutdown the plugin (called when unloaded).
230    fn shutdown(&mut self) -> Result<()> {
231        Ok(())
232    }
233}
234
235/// A boxed plugin.
236pub type BoxedPlugin = Box<dyn Plugin>;
237
238/// Plugin manager for loading and managing plugins.
239pub struct PluginManager {
240    plugins: RwLock<Vec<BoxedPlugin>>,
241    registry: Arc<FactoryRegistry>,
242}
243
244impl PluginManager {
245    /// Create a new plugin manager.
246    pub fn new(registry: Arc<FactoryRegistry>) -> Self {
247        Self {
248            plugins: RwLock::new(Vec::new()),
249            registry,
250        }
251    }
252
253    /// Load a plugin.
254    pub fn load<P: Plugin + 'static>(&self, mut plugin: P) -> Result<()> {
255        plugin.initialize()?;
256        plugin.register_factories(&self.registry)?;
257
258        self.plugins.write().push(Box::new(plugin));
259        Ok(())
260    }
261
262    /// Load a boxed plugin.
263    pub fn load_boxed(&self, mut plugin: BoxedPlugin) -> Result<()> {
264        plugin.initialize()?;
265        plugin.register_factories(&self.registry)?;
266
267        self.plugins.write().push(plugin);
268        Ok(())
269    }
270
271    /// Get the number of loaded plugins.
272    pub fn plugin_count(&self) -> usize {
273        self.plugins.read().len()
274    }
275
276    /// Get information about loaded plugins.
277    pub fn plugin_info(&self) -> Vec<PluginInfo> {
278        self.plugins
279            .read()
280            .iter()
281            .map(|p| PluginInfo {
282                name: p.name().to_string(),
283                version: p.version().to_string(),
284                description: p.description().to_string(),
285            })
286            .collect()
287    }
288
289    /// Get the factory registry.
290    pub fn registry(&self) -> &Arc<FactoryRegistry> {
291        &self.registry
292    }
293
294    /// Shutdown all plugins.
295    pub fn shutdown_all(&self) -> Result<()> {
296        for plugin in self.plugins.write().iter_mut() {
297            plugin.shutdown()?;
298        }
299        Ok(())
300    }
301}
302
303/// Information about a loaded plugin.
304#[derive(Debug, Clone)]
305pub struct PluginInfo {
306    /// Plugin name.
307    pub name: String,
308    /// Plugin version.
309    pub version: String,
310    /// Plugin description.
311    pub description: String,
312}
313
314#[cfg(test)]
315mod tests {
316    use super::*;
317    use crate::device::{Device, DeviceInfo, DeviceStatistics};
318    use crate::tags::Tags;
319    use crate::types::{DataPoint, DataPointDef, DataPointId};
320    use crate::value::Value;
321    use async_trait::async_trait;
322
323    // Mock device for testing
324    struct MockDevice {
325        info: DeviceInfo,
326    }
327
328    impl MockDevice {
329        fn new(id: &str, name: &str) -> Self {
330            Self {
331                info: DeviceInfo::new(id, name, Protocol::ModbusTcp),
332            }
333        }
334    }
335
336    #[async_trait]
337    impl Device for MockDevice {
338        fn info(&self) -> &DeviceInfo {
339            &self.info
340        }
341
342        async fn initialize(&mut self) -> Result<()> {
343            Ok(())
344        }
345
346        async fn start(&mut self) -> Result<()> {
347            Ok(())
348        }
349
350        async fn stop(&mut self) -> Result<()> {
351            Ok(())
352        }
353
354        async fn tick(&mut self) -> Result<()> {
355            Ok(())
356        }
357
358        fn point_definitions(&self) -> Vec<&DataPointDef> {
359            vec![]
360        }
361
362        fn point_definition(&self, _point_id: &str) -> Option<&DataPointDef> {
363            None
364        }
365
366        async fn read(&self, point_id: &str) -> Result<DataPoint> {
367            Ok(DataPoint::new(
368                DataPointId::new(&self.info.id, point_id),
369                Value::F64(0.0),
370            ))
371        }
372
373        async fn write(&mut self, _point_id: &str, _value: Value) -> Result<()> {
374            Ok(())
375        }
376
377        fn statistics(&self) -> DeviceStatistics {
378            DeviceStatistics::default()
379        }
380    }
381
382    // Mock factory for testing
383    struct MockFactory;
384
385    impl DeviceFactory for MockFactory {
386        fn protocol(&self) -> Protocol {
387            Protocol::ModbusTcp
388        }
389
390        fn create(&self, config: DeviceConfig) -> Result<BoxedDevice> {
391            Ok(Box::new(MockDevice::new(&config.id, &config.name)))
392        }
393    }
394
395    // Mock plugin for testing
396    struct MockPlugin {
397        name: String,
398    }
399
400    impl MockPlugin {
401        fn new(name: &str) -> Self {
402            Self {
403                name: name.to_string(),
404            }
405        }
406    }
407
408    impl Plugin for MockPlugin {
409        fn name(&self) -> &str {
410            &self.name
411        }
412
413        fn description(&self) -> &str {
414            "Mock plugin for testing"
415        }
416
417        fn register_factories(&self, registry: &FactoryRegistry) -> Result<()> {
418            registry.register(MockFactory)
419        }
420    }
421
422    #[test]
423    fn test_factory_registry() {
424        let registry = FactoryRegistry::new();
425
426        assert!(!registry.has(Protocol::ModbusTcp));
427
428        registry.register(MockFactory).unwrap();
429
430        assert!(registry.has(Protocol::ModbusTcp));
431        assert!(registry.protocols().contains(&Protocol::ModbusTcp));
432    }
433
434    #[test]
435    fn test_factory_metadata_uses_release_version() {
436        let metadata = MockFactory.metadata();
437        assert_eq!(metadata.version, RELEASE_VERSION);
438    }
439
440    #[test]
441    fn test_factory_create_device() {
442        let registry = FactoryRegistry::new();
443        registry.register(MockFactory).unwrap();
444
445        let config = DeviceConfig {
446            id: "test-001".to_string(),
447            name: "Test Device".to_string(),
448            description: String::new(),
449            protocol: Protocol::ModbusTcp,
450            address: None,
451            points: vec![],
452            metadata: Default::default(),
453            tags: Tags::new(),
454        };
455
456        let device = registry.create_device(config).unwrap();
457        assert_eq!(device.id(), "test-001");
458    }
459
460    #[test]
461    fn test_factory_validation() {
462        let factory = MockFactory;
463
464        // Empty ID should fail
465        let config = DeviceConfig {
466            id: String::new(),
467            name: "Test".to_string(),
468            description: String::new(),
469            protocol: Protocol::ModbusTcp,
470            address: None,
471            points: vec![],
472            metadata: Default::default(),
473            tags: Tags::new(),
474        };
475        assert!(factory.validate(&config).is_err());
476
477        // Wrong protocol should fail
478        let config = DeviceConfig {
479            id: "test".to_string(),
480            name: "Test".to_string(),
481            description: String::new(),
482            protocol: Protocol::OpcUa,
483            address: None,
484            points: vec![],
485            metadata: Default::default(),
486            tags: Tags::new(),
487        };
488        assert!(factory.validate(&config).is_err());
489    }
490
491    #[test]
492    fn test_plugin_manager() {
493        let registry = Arc::new(FactoryRegistry::new());
494        let manager = PluginManager::new(registry.clone());
495
496        assert_eq!(manager.plugin_count(), 0);
497
498        manager.load(MockPlugin::new("test-plugin")).unwrap();
499
500        assert_eq!(manager.plugin_count(), 1);
501        assert!(registry.has(Protocol::ModbusTcp));
502
503        let info = manager.plugin_info();
504        assert_eq!(info[0].name, "test-plugin");
505        assert_eq!(info[0].version, RELEASE_VERSION);
506    }
507
508    #[test]
509    fn test_duplicate_factory_registration() {
510        let registry = FactoryRegistry::new();
511
512        registry.register(MockFactory).unwrap();
513        let result = registry.register(MockFactory);
514
515        assert!(result.is_err());
516    }
517}