ryo_plugin_runtime/
registry.rs1use ryo_plugin_loader::{LoadedPlugin, LoaderError, PluginLoader, TransformDef};
4use std::collections::HashMap;
5use std::path::Path;
6
7#[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
23pub struct MutationRegistry {
28 loader: PluginLoader,
30 plugins: HashMap<String, LoadedPlugin>,
32}
33
34impl MutationRegistry {
35 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 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 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 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 pub fn get_plugin(&self, name: &str) -> Option<&LoadedPlugin> {
100 self.plugins.get(name)
101 }
102
103 pub fn get_plugin_mut(&mut self, name: &str) -> Option<&mut LoadedPlugin> {
105 self.plugins.get_mut(name)
106 }
107
108 pub fn has_plugin(&self, name: &str) -> bool {
110 self.plugins.contains_key(name)
111 }
112
113 pub fn plugin_names(&self) -> Vec<&str> {
115 self.plugins.keys().map(|s| s.as_str()).collect()
116 }
117
118 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#[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}