Skip to main content

pro_plugin/
manifest.rs

1//! Plugin manifest and configuration
2
3use serde::{Deserialize, Serialize};
4
5use crate::hooks::Hook;
6
7/// Plugin manifest (plugin.toml or embedded in wasm)
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct PluginManifest {
10    /// Plugin name
11    pub name: String,
12
13    /// Plugin version
14    pub version: String,
15
16    /// Plugin description
17    #[serde(default)]
18    pub description: String,
19
20    /// Plugin author
21    #[serde(default)]
22    pub author: Option<String>,
23
24    /// License
25    #[serde(default)]
26    pub license: Option<String>,
27
28    /// Homepage/repository URL
29    #[serde(default)]
30    pub homepage: Option<String>,
31
32    /// Minimum rx version required
33    #[serde(default)]
34    pub min_rx_version: Option<String>,
35
36    /// Hooks this plugin implements
37    #[serde(default)]
38    pub hooks: Vec<String>,
39
40    /// Permissions requested by the plugin
41    #[serde(default)]
42    pub permissions: PluginPermissions,
43}
44
45impl PluginManifest {
46    /// Create a new manifest
47    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    /// Check if the plugin implements a specific hook
62    pub fn has_hook(&self, hook: Hook) -> bool {
63        self.hooks.contains(&hook.function_name().to_string())
64    }
65
66    /// Parse from TOML string
67    pub fn from_toml(content: &str) -> Result<Self, toml::de::Error> {
68        toml::from_str(content)
69    }
70
71    /// Serialize to TOML string
72    pub fn to_toml(&self) -> Result<String, toml::ser::Error> {
73        toml::to_string_pretty(self)
74    }
75}
76
77/// Permissions that a plugin can request
78#[derive(Debug, Clone, Default, Serialize, Deserialize)]
79pub struct PluginPermissions {
80    /// Can read files in project directory
81    #[serde(default)]
82    pub read_files: bool,
83
84    /// Can write files in project directory
85    #[serde(default)]
86    pub write_files: bool,
87
88    /// Can make network requests
89    #[serde(default)]
90    pub network: bool,
91
92    /// Can read environment variables
93    #[serde(default)]
94    pub env_vars: bool,
95
96    /// Can execute shell commands
97    #[serde(default)]
98    pub execute: bool,
99
100    /// Allowed file patterns for read access
101    #[serde(default)]
102    pub allowed_read_paths: Vec<String>,
103
104    /// Allowed file patterns for write access
105    #[serde(default)]
106    pub allowed_write_paths: Vec<String>,
107
108    /// Allowed network hosts
109    #[serde(default)]
110    pub allowed_hosts: Vec<String>,
111}
112
113impl PluginPermissions {
114    /// Create permissions with no access
115    pub fn none() -> Self {
116        Self::default()
117    }
118
119    /// Create permissions with read-only access
120    pub fn read_only() -> Self {
121        Self {
122            read_files: true,
123            ..Default::default()
124        }
125    }
126
127    /// Create permissions with full file access
128    pub fn full_file_access() -> Self {
129        Self {
130            read_files: true,
131            write_files: true,
132            ..Default::default()
133        }
134    }
135}
136
137/// Plugin configuration from pyproject.toml [tool.rx.plugins]
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct PluginConfig {
140    /// Plugin source (local path or URL)
141    pub source: String,
142
143    /// Override permissions (grant more or deny)
144    #[serde(default)]
145    pub permissions: Option<PluginPermissions>,
146
147    /// Plugin-specific settings
148    #[serde(default)]
149    pub settings: serde_json::Value,
150
151    /// Whether the plugin is enabled
152    #[serde(default = "default_enabled")]
153    pub enabled: bool,
154}
155
156fn default_enabled() -> bool {
157    true
158}
159
160impl PluginConfig {
161    /// Create a new plugin config from source
162    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}