devalang_core/core/plugin/
loader.rs1use serde::Deserialize;
2use std::path::Path;
3use toml::Value as TomlValue;
4
5#[derive(Debug, Deserialize, Clone)]
6pub struct PluginInfo {
7 pub name: String,
8 pub version: Option<String>,
9 pub description: Option<String>,
10 pub author: Option<String>,
11 #[serde(skip)]
12 pub exports: Vec<ExportEntry>,
13}
14
15#[derive(Debug, Deserialize, Clone)]
16pub struct ExportEntry {
17 pub name: String,
18 #[serde(rename = "type")]
19 pub kind: String,
20 #[serde(default)]
21 pub default: Option<TomlValue>,
22}
23
24#[derive(Debug, Deserialize, Clone)]
25pub struct PluginFile {
26 pub plugin: PluginInfo,
27 #[serde(default)]
28 pub export: Vec<ExportEntry>,
29}
30
31pub fn load_plugin(author: &str, name: &str) -> Result<(PluginInfo, Vec<u8>), String> {
33 let root = Path::new("./.deva");
35 let plugin_dir_preferred = root.join("plugin").join(format!("{}.{}", author, name));
37 let toml_path_preferred = plugin_dir_preferred.join("plugin.toml");
38 let wasm_path_preferred_bg = plugin_dir_preferred.join(format!("{}_bg.wasm", name));
39 let wasm_path_preferred_plain = plugin_dir_preferred.join(format!("{}.wasm", name));
40
41 let plugin_dir_fallback = root.join("plugin").join(author).join(name);
43 let toml_path_fallback = plugin_dir_fallback.join("plugin.toml");
44 let wasm_path_fallback_bg = plugin_dir_fallback.join(format!("{}_bg.wasm", name));
45 let wasm_path_fallback_plain = plugin_dir_fallback.join(format!("{}.wasm", name));
46
47 let (toml_path, wasm_path) = if toml_path_preferred.exists() && wasm_path_preferred_bg.exists()
49 {
50 (toml_path_preferred, wasm_path_preferred_bg)
51 } else if toml_path_preferred.exists() && wasm_path_preferred_plain.exists() {
52 (toml_path_preferred, wasm_path_preferred_plain)
53 } else if toml_path_fallback.exists() && wasm_path_fallback_bg.exists() {
54 (toml_path_fallback, wasm_path_fallback_bg)
55 } else if toml_path_fallback.exists() && wasm_path_fallback_plain.exists() {
56 (toml_path_fallback, wasm_path_fallback_plain)
57 } else {
58 if !toml_path_preferred.exists() {
60 return Err(format!(
61 "❌ Plugin file not found: {}",
62 toml_path_preferred.display()
63 ));
64 }
65 if !wasm_path_preferred_bg.exists() && !wasm_path_preferred_plain.exists() {
66 return Err(format!(
67 "❌ Plugin wasm not found: '{}' or '{}'",
68 wasm_path_preferred_bg.display(),
69 wasm_path_preferred_plain.display()
70 ));
71 }
72 unreachable!();
73 };
74
75 let toml_content = std::fs::read_to_string(&toml_path)
76 .map_err(|e| format!("Failed to read '{}': {}", toml_path.display(), e))?;
77 let plugin_file: PluginFile = toml::from_str(&toml_content)
78 .map_err(|e| format!("Failed to parse '{}': {}", toml_path.display(), e))?;
79
80 let wasm_bytes = std::fs::read(&wasm_path)
81 .map_err(|e| format!("Failed to read '{}': {}", wasm_path.display(), e))?;
82
83 let mut info = plugin_file.plugin.clone();
84 info.exports = plugin_file.export.clone();
85
86 Ok((info, wasm_bytes))
87}
88
89pub fn load_plugin_from_dot(dot: &str) -> Result<(PluginInfo, Vec<u8>), String> {
91 let mut parts = dot.split('.');
92 let author = parts
93 .next()
94 .ok_or_else(|| "Invalid plugin name, missing author".to_string())?;
95 let name = parts
96 .next()
97 .ok_or_else(|| "Invalid plugin name, missing name".to_string())?;
98 if parts.next().is_some() {
99 return Err("Invalid plugin name format, expected <author>.<name>".into());
100 }
101 load_plugin(author, name)
102}
103
104pub fn load_plugin_from_uri(uri: &str) -> Result<(PluginInfo, Vec<u8>), String> {
105 if !uri.starts_with("devalang://plugin/") {
106 return Err("Invalid plugin URI".into());
107 }
108
109 let payload = uri.trim_start_matches("devalang://plugin/");
111 let mut parts = payload.split('.');
112 let author = parts
113 .next()
114 .ok_or_else(|| "Invalid plugin URI, missing author".to_string())?;
115 let name = parts
116 .next()
117 .ok_or_else(|| "Invalid plugin URI, missing name".to_string())?;
118 if parts.next().is_some() {
119 return Err("Invalid plugin URI format, expected devalang://plugin/<author>.<name>".into());
120 }
121
122 load_plugin(author, name)
123}