Skip to main content

rma_plugins/
loader.rs

1//! Plugin loading utilities
2
3use crate::PluginError;
4use std::path::{Path, PathBuf};
5use tracing::{debug, info};
6
7/// Default plugin directories
8pub fn default_plugin_dirs() -> Vec<PathBuf> {
9    let mut dirs = Vec::new();
10
11    // Project-local plugins
12    dirs.push(PathBuf::from(".rma/plugins"));
13
14    // User plugins
15    if let Some(home) = dirs::home_dir() {
16        dirs.push(home.join(".config/rma/plugins"));
17    }
18
19    // System plugins
20    #[cfg(unix)]
21    dirs.push(PathBuf::from("/usr/share/rma/plugins"));
22
23    dirs
24}
25
26/// Discover all WASM plugins in the given directories
27pub fn discover_plugins(dirs: &[PathBuf]) -> Vec<PathBuf> {
28    let mut plugins = Vec::new();
29
30    for dir in dirs {
31        if !dir.exists() {
32            debug!("Plugin directory {:?} does not exist", dir);
33            continue;
34        }
35
36        match std::fs::read_dir(dir) {
37            Ok(entries) => {
38                for entry in entries.filter_map(|e| e.ok()) {
39                    let path = entry.path();
40                    if path.extension().map(|e| e == "wasm").unwrap_or(false) {
41                        info!("Discovered plugin: {:?}", path);
42                        plugins.push(path);
43                    }
44                }
45            }
46            Err(e) => {
47                debug!("Failed to read plugin directory {:?}: {}", dir, e);
48            }
49        }
50    }
51
52    plugins
53}
54
55/// Validate a WASM plugin file
56pub fn validate_plugin(path: &Path) -> Result<(), PluginError> {
57    // Check file exists
58    if !path.exists() {
59        return Err(PluginError::NotFound(path.display().to_string()));
60    }
61
62    // Check extension
63    if path.extension().map(|e| e != "wasm").unwrap_or(true) {
64        return Err(PluginError::LoadError(
65            "File must have .wasm extension".into(),
66        ));
67    }
68
69    // Check file size (max 10MB)
70    let metadata = std::fs::metadata(path)
71        .map_err(|e| PluginError::LoadError(format!("Failed to read metadata: {}", e)))?;
72
73    if metadata.len() > 10 * 1024 * 1024 {
74        return Err(PluginError::LoadError(
75            "Plugin file exceeds 10MB limit".into(),
76        ));
77    }
78
79    // Basic WASM magic number check
80    let mut file = std::fs::File::open(path)
81        .map_err(|e| PluginError::LoadError(format!("Failed to open file: {}", e)))?;
82
83    let mut magic = [0u8; 4];
84    use std::io::Read;
85    file.read_exact(&mut magic)
86        .map_err(|e| PluginError::LoadError(format!("Failed to read file: {}", e)))?;
87
88    if magic != [0x00, 0x61, 0x73, 0x6D] {
89        return Err(PluginError::LoadError("Invalid WASM magic number".into()));
90    }
91
92    Ok(())
93}
94
95/// Plugin manifest file structure
96#[derive(Debug, Clone, serde::Deserialize)]
97pub struct PluginManifest {
98    pub name: String,
99    pub version: String,
100    pub description: String,
101    pub author: Option<String>,
102    pub wasm_file: String,
103    pub languages: Vec<String>,
104    pub rules: Vec<RuleDefinition>,
105}
106
107#[derive(Debug, Clone, serde::Deserialize)]
108pub struct RuleDefinition {
109    pub id: String,
110    pub name: String,
111    pub description: String,
112    pub severity: String,
113}
114
115/// Load plugin manifest from JSON file
116pub fn load_manifest(path: &Path) -> Result<PluginManifest, PluginError> {
117    let content = std::fs::read_to_string(path)
118        .map_err(|e| PluginError::LoadError(format!("Failed to read manifest: {}", e)))?;
119
120    serde_json::from_str(&content)
121        .map_err(|e| PluginError::LoadError(format!("Invalid manifest JSON: {}", e)))
122}