1use serde::{Deserialize, Serialize};
4
5use crate::hooks::Hook;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct PluginManifest {
10 pub name: String,
12
13 pub version: String,
15
16 #[serde(default)]
18 pub description: String,
19
20 #[serde(default)]
22 pub author: Option<String>,
23
24 #[serde(default)]
26 pub license: Option<String>,
27
28 #[serde(default)]
30 pub homepage: Option<String>,
31
32 #[serde(default)]
34 pub min_rx_version: Option<String>,
35
36 #[serde(default)]
38 pub hooks: Vec<String>,
39
40 #[serde(default)]
42 pub permissions: PluginPermissions,
43}
44
45impl PluginManifest {
46 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
48 Self {
49 name: name.into(),
50 version: version.into(),
51 description: String::new(),
52 author: None,
53 license: None,
54 homepage: None,
55 min_rx_version: None,
56 hooks: Vec::new(),
57 permissions: PluginPermissions::default(),
58 }
59 }
60
61 pub fn has_hook(&self, hook: Hook) -> bool {
63 self.hooks.contains(&hook.function_name().to_string())
64 }
65
66 pub fn from_toml(content: &str) -> Result<Self, toml::de::Error> {
68 toml::from_str(content)
69 }
70
71 pub fn to_toml(&self) -> Result<String, toml::ser::Error> {
73 toml::to_string_pretty(self)
74 }
75}
76
77#[derive(Debug, Clone, Default, Serialize, Deserialize)]
79pub struct PluginPermissions {
80 #[serde(default)]
82 pub read_files: bool,
83
84 #[serde(default)]
86 pub write_files: bool,
87
88 #[serde(default)]
90 pub network: bool,
91
92 #[serde(default)]
94 pub env_vars: bool,
95
96 #[serde(default)]
98 pub execute: bool,
99
100 #[serde(default)]
102 pub allowed_read_paths: Vec<String>,
103
104 #[serde(default)]
106 pub allowed_write_paths: Vec<String>,
107
108 #[serde(default)]
110 pub allowed_hosts: Vec<String>,
111}
112
113impl PluginPermissions {
114 pub fn none() -> Self {
116 Self::default()
117 }
118
119 pub fn read_only() -> Self {
121 Self {
122 read_files: true,
123 ..Default::default()
124 }
125 }
126
127 pub fn full_file_access() -> Self {
129 Self {
130 read_files: true,
131 write_files: true,
132 ..Default::default()
133 }
134 }
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct PluginConfig {
140 pub source: String,
142
143 #[serde(default)]
145 pub permissions: Option<PluginPermissions>,
146
147 #[serde(default)]
149 pub settings: serde_json::Value,
150
151 #[serde(default = "default_enabled")]
153 pub enabled: bool,
154}
155
156fn default_enabled() -> bool {
157 true
158}
159
160impl PluginConfig {
161 pub fn new(source: impl Into<String>) -> Self {
163 Self {
164 source: source.into(),
165 permissions: None,
166 settings: serde_json::Value::Null,
167 enabled: true,
168 }
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_manifest_from_toml() {
178 let toml = r#"
179name = "my-plugin"
180version = "1.0.0"
181description = "A test plugin"
182hooks = ["pre_build", "post_build"]
183
184[permissions]
185read_files = true
186write_files = false
187"#;
188
189 let manifest = PluginManifest::from_toml(toml).unwrap();
190 assert_eq!(manifest.name, "my-plugin");
191 assert_eq!(manifest.version, "1.0.0");
192 assert!(manifest.has_hook(Hook::PreBuild));
193 assert!(manifest.has_hook(Hook::PostBuild));
194 assert!(!manifest.has_hook(Hook::PreResolve));
195 assert!(manifest.permissions.read_files);
196 assert!(!manifest.permissions.write_files);
197 }
198
199 #[test]
200 fn test_manifest_to_toml() {
201 let manifest = PluginManifest::new("test", "0.1.0");
202 let toml = manifest.to_toml().unwrap();
203 assert!(toml.contains("name = \"test\""));
204 }
205}