Skip to main content

xtalforge_modules/
registry.rs

1//! Generic module registry (Task 3.13).
2
3use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6use xtalforge_core::Error;
7
8/// Description of one module.
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct ModuleInfo {
11    /// Stable name.
12    pub name: String,
13    /// One-line description.
14    pub description: String,
15    /// Version of the module implementation.
16    pub version: String,
17    /// Parameter names it accepts.
18    pub parameters: Vec<String>,
19}
20
21/// A module that can be registered.
22pub trait Module: std::fmt::Debug + Send + Sync {
23    /// Description.
24    fn info(&self) -> ModuleInfo;
25
26    /// Whether the module can run in this build and environment.
27    ///
28    /// The default is `true`; modules that need an external program or an
29    /// optional feature override it with a real probe.
30    fn available(&self) -> bool {
31        true
32    }
33
34    /// Runs the module with JSON parameters and returns a JSON result.
35    ///
36    /// # Errors
37    ///
38    /// The default implementation returns [`Error::InvalidInput`]; modules
39    /// that can run override it.
40    fn run(&self, _parameters: &serde_json::Value) -> Result<serde_json::Value, Error> {
41        Err(Error::InvalidInput(format!(
42            "module {} has no run implementation",
43            self.info().name
44        )))
45    }
46}
47
48/// Registry of modules by name.
49#[derive(Debug, Default)]
50pub struct ModuleRegistry {
51    modules: BTreeMap<String, Box<dyn Module>>,
52}
53
54impl ModuleRegistry {
55    /// Empty registry.
56    #[must_use]
57    pub fn new() -> Self {
58        Self::default()
59    }
60
61    /// Registers a module.
62    ///
63    /// # Errors
64    ///
65    /// Returns [`Error::InvalidInput`] for a duplicate or empty name.
66    pub fn register(&mut self, module: Box<dyn Module>) -> Result<(), Error> {
67        let info = module.info();
68        if info.name.trim().is_empty() {
69            return Err(Error::InvalidInput(
70                "a module needs a non-empty name".to_owned(),
71            ));
72        }
73        if self.modules.contains_key(&info.name) {
74            return Err(Error::InvalidInput(format!(
75                "module {} is already registered",
76                info.name
77            )));
78        }
79        self.modules.insert(info.name, module);
80        Ok(())
81    }
82
83    /// Unregisters a module.
84    pub fn unregister(&mut self, name: &str) -> bool {
85        self.modules.remove(name).is_some()
86    }
87
88    /// Gets a module.
89    #[must_use]
90    pub fn get(&self, name: &str) -> Option<&dyn Module> {
91        self.modules.get(name).map(Box::as_ref)
92    }
93
94    /// Lists all module descriptions.
95    #[must_use]
96    pub fn list(&self) -> Vec<ModuleInfo> {
97        self.modules.values().map(|module| module.info()).collect()
98    }
99}