1use crate::PluginError;
4use std::path::{Path, PathBuf};
5use tracing::{debug, info};
6
7pub fn default_plugin_dirs() -> Vec<PathBuf> {
9 let mut dirs = Vec::new();
10
11 dirs.push(PathBuf::from(".rma/plugins"));
13
14 if let Some(home) = dirs::home_dir() {
16 dirs.push(home.join(".config/rma/plugins"));
17 }
18
19 #[cfg(unix)]
21 dirs.push(PathBuf::from("/usr/share/rma/plugins"));
22
23 dirs
24}
25
26pub 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
55pub fn validate_plugin(path: &Path) -> Result<(), PluginError> {
57 if !path.exists() {
59 return Err(PluginError::NotFound(path.display().to_string()));
60 }
61
62 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 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 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#[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
115pub 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}