use ryo_plugin_loader::{LoadedPlugin, LoaderError, PluginLoader, TransformDef};
use std::collections::HashMap;
use std::path::Path;
#[derive(Debug, thiserror::Error)]
pub enum RegistryError {
#[error("Failed to create plugin loader: {0}")]
LoaderCreation(#[source] LoaderError),
#[error("Failed to load plugin: {0}")]
PluginLoad(#[source] LoaderError),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Plugin not found: {0}")]
PluginNotFound(String),
}
pub struct MutationRegistry {
loader: PluginLoader,
plugins: HashMap<String, LoadedPlugin>,
}
impl MutationRegistry {
pub fn new() -> Result<Self, RegistryError> {
let loader = PluginLoader::new().map_err(RegistryError::LoaderCreation)?;
Ok(Self {
loader,
plugins: HashMap::new(),
})
}
pub fn load_plugin(&mut self, wasm_bytes: &[u8]) -> Result<String, RegistryError> {
let plugin = self
.loader
.load(wasm_bytes)
.map_err(RegistryError::PluginLoad)?;
let name = plugin.manifest.name.clone();
tracing::info!(
"Registered plugin mutation: {} (tier {})",
name,
plugin.manifest.tier
);
self.plugins.insert(name.clone(), plugin);
Ok(name)
}
pub fn load_plugins_from_dir(&mut self, dir: &Path) -> Result<Vec<String>, RegistryError> {
let mut loaded = Vec::new();
if !dir.exists() {
tracing::debug!("Plugin directory does not exist: {:?}", dir);
return Ok(loaded);
}
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().is_some_and(|ext| ext == "wasm") {
match std::fs::read(&path) {
Ok(bytes) => match self.load_plugin(&bytes) {
Ok(name) => {
tracing::info!("Loaded plugin from {:?}: {}", path, name);
loaded.push(name);
}
Err(e) => {
tracing::warn!("Failed to load plugin {:?}: {}", path, e);
}
},
Err(e) => {
tracing::warn!("Failed to read plugin file {:?}: {}", path, e);
}
}
}
}
Ok(loaded)
}
pub fn get_plugin(&self, name: &str) -> Option<&LoadedPlugin> {
self.plugins.get(name)
}
pub fn get_plugin_mut(&mut self, name: &str) -> Option<&mut LoadedPlugin> {
self.plugins.get_mut(name)
}
pub fn has_plugin(&self, name: &str) -> bool {
self.plugins.contains_key(name)
}
pub fn plugin_names(&self) -> Vec<&str> {
self.plugins.keys().map(|s| s.as_str()).collect()
}
pub fn plugin_info(&self) -> Vec<PluginInfo> {
self.plugins
.values()
.map(|p| PluginInfo {
name: p.manifest.name.clone(),
description: p.manifest.description.clone(),
tier: p.manifest.tier,
is_template: matches!(p.manifest.transform, TransformDef::Template(_)),
})
.collect()
}
}
#[derive(Debug, Clone)]
pub struct PluginInfo {
pub name: String,
pub description: String,
pub tier: u8,
pub is_template: bool,
}
impl std::fmt::Display for PluginInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let transform_type = if self.is_template { "template" } else { "wasm" };
write!(
f,
"{} (tier {}, {}): {}",
self.name, self.tier, transform_type, self.description
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registry_creation() {
let registry = MutationRegistry::new();
assert!(registry.is_ok());
}
}