Skip to main content

ryo_plugin_runtime/
registry.rs

1//! Mutation Registry - Unified access to builtin and plugin mutations
2
3use ryo_plugin_loader::{LoadedPlugin, LoaderError, PluginLoader, TransformDef};
4use std::collections::HashMap;
5use std::path::Path;
6
7/// Error type for registry operations
8#[derive(Debug, thiserror::Error)]
9pub enum RegistryError {
10    #[error("Failed to create plugin loader: {0}")]
11    LoaderCreation(#[source] LoaderError),
12
13    #[error("Failed to load plugin: {0}")]
14    PluginLoad(#[source] LoaderError),
15
16    #[error("IO error: {0}")]
17    Io(#[from] std::io::Error),
18
19    #[error("Plugin not found: {0}")]
20    PluginNotFound(String),
21}
22
23/// Registry of all available mutations (builtin + plugins)
24///
25/// Provides unified access to both built-in Rust mutations and
26/// dynamically loaded WASM plugins.
27pub struct MutationRegistry {
28    /// Plugin loader instance
29    loader: PluginLoader,
30    /// Loaded WASM plugins by name
31    plugins: HashMap<String, LoadedPlugin>,
32}
33
34impl MutationRegistry {
35    /// Create a new registry with plugin support
36    pub fn new() -> Result<Self, RegistryError> {
37        let loader = PluginLoader::new().map_err(RegistryError::LoaderCreation)?;
38
39        Ok(Self {
40            loader,
41            plugins: HashMap::new(),
42        })
43    }
44
45    /// Load a single plugin from bytes
46    pub fn load_plugin(&mut self, wasm_bytes: &[u8]) -> Result<String, RegistryError> {
47        let plugin = self
48            .loader
49            .load(wasm_bytes)
50            .map_err(RegistryError::PluginLoad)?;
51        let name = plugin.manifest.name.clone();
52
53        tracing::info!(
54            "Registered plugin mutation: {} (tier {})",
55            name,
56            plugin.manifest.tier
57        );
58
59        self.plugins.insert(name.clone(), plugin);
60        Ok(name)
61    }
62
63    /// Load all plugins from a directory
64    pub fn load_plugins_from_dir(&mut self, dir: &Path) -> Result<Vec<String>, RegistryError> {
65        let mut loaded = Vec::new();
66
67        if !dir.exists() {
68            tracing::debug!("Plugin directory does not exist: {:?}", dir);
69            return Ok(loaded);
70        }
71
72        for entry in std::fs::read_dir(dir)? {
73            let entry = entry?;
74            let path = entry.path();
75
76            // Only load .wasm files
77            if path.extension().is_some_and(|ext| ext == "wasm") {
78                match std::fs::read(&path) {
79                    Ok(bytes) => match self.load_plugin(&bytes) {
80                        Ok(name) => {
81                            tracing::info!("Loaded plugin from {:?}: {}", path, name);
82                            loaded.push(name);
83                        }
84                        Err(e) => {
85                            tracing::warn!("Failed to load plugin {:?}: {}", path, e);
86                        }
87                    },
88                    Err(e) => {
89                        tracing::warn!("Failed to read plugin file {:?}: {}", path, e);
90                    }
91                }
92            }
93        }
94
95        Ok(loaded)
96    }
97
98    /// Get a loaded plugin by name
99    pub fn get_plugin(&self, name: &str) -> Option<&LoadedPlugin> {
100        self.plugins.get(name)
101    }
102
103    /// Get a mutable reference to a loaded plugin
104    pub fn get_plugin_mut(&mut self, name: &str) -> Option<&mut LoadedPlugin> {
105        self.plugins.get_mut(name)
106    }
107
108    /// Check if a plugin is loaded
109    pub fn has_plugin(&self, name: &str) -> bool {
110        self.plugins.contains_key(name)
111    }
112
113    /// Get all loaded plugin names
114    pub fn plugin_names(&self) -> Vec<&str> {
115        self.plugins.keys().map(|s| s.as_str()).collect()
116    }
117
118    /// Get plugin info for display
119    pub fn plugin_info(&self) -> Vec<PluginInfo> {
120        self.plugins
121            .values()
122            .map(|p| PluginInfo {
123                name: p.manifest.name.clone(),
124                description: p.manifest.description.clone(),
125                tier: p.manifest.tier,
126                is_template: matches!(p.manifest.transform, TransformDef::Template(_)),
127            })
128            .collect()
129    }
130}
131
132/// Information about a loaded plugin
133#[derive(Debug, Clone)]
134pub struct PluginInfo {
135    pub name: String,
136    pub description: String,
137    pub tier: u8,
138    pub is_template: bool,
139}
140
141impl std::fmt::Display for PluginInfo {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        let transform_type = if self.is_template { "template" } else { "wasm" };
144        write!(
145            f,
146            "{} (tier {}, {}): {}",
147            self.name, self.tier, transform_type, self.description
148        )
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_registry_creation() {
158        let registry = MutationRegistry::new();
159        assert!(registry.is_ok());
160    }
161}