vx_plugin/
plugin.rs

1//! Combined plugin trait and plugin management functionality
2//!
3//! This module defines the `VxPlugin` trait, which is the main interface for
4//! creating plugins that can provide both tools and package managers.
5
6use crate::{Result, VxPackageManager, VxTool};
7use async_trait::async_trait;
8use std::collections::HashMap;
9
10/// Combined plugin trait that can provide both tools and package managers
11///
12/// This is the main trait that plugin developers implement to register their functionality.
13/// A plugin can provide tools, package managers, or both.
14///
15/// # Example
16///
17/// ```rust,no_run
18/// use vx_plugin::{VxPlugin, VxTool, VxPackageManager, Result};
19/// use async_trait::async_trait;
20///
21/// struct MyPlugin;
22///
23/// #[async_trait]
24/// impl VxPlugin for MyPlugin {
25///     fn name(&self) -> &str {
26///         "my-plugin"
27///     }
28///
29///     fn description(&self) -> &str {
30///         "A plugin that provides custom tools and package managers"
31///     }
32///
33///     fn tools(&self) -> Vec<Box<dyn VxTool>> {
34///         // Return your tool implementations
35///         vec![]
36///     }
37///
38///     fn package_managers(&self) -> Vec<Box<dyn VxPackageManager>> {
39///         // Return your package manager implementations
40///         vec![]
41///     }
42/// }
43/// ```
44#[async_trait]
45pub trait VxPlugin: Send + Sync {
46    /// Plugin name (required)
47    ///
48    /// This should be a unique identifier for the plugin.
49    fn name(&self) -> &str;
50
51    /// Plugin description (optional)
52    ///
53    /// A human-readable description of what this plugin provides.
54    fn description(&self) -> &str {
55        "A vx plugin"
56    }
57
58    /// Plugin version (optional)
59    ///
60    /// The version of this plugin implementation.
61    fn version(&self) -> &str {
62        "0.1.0"
63    }
64
65    /// Plugin author (optional)
66    ///
67    /// Information about who created this plugin.
68    fn author(&self) -> Option<&str> {
69        None
70    }
71
72    /// Plugin homepage or repository URL (optional)
73    ///
74    /// URL where users can find more information about this plugin.
75    fn homepage(&self) -> Option<&str> {
76        None
77    }
78
79    /// Get all tools provided by this plugin
80    ///
81    /// Return a vector of tool implementations that this plugin provides.
82    /// Return an empty vector if this plugin doesn't provide any tools.
83    fn tools(&self) -> Vec<Box<dyn VxTool>> {
84        vec![]
85    }
86
87    /// Get all package managers provided by this plugin
88    ///
89    /// Return a vector of package manager implementations that this plugin provides.
90    /// Return an empty vector if this plugin doesn't provide any package managers.
91    fn package_managers(&self) -> Vec<Box<dyn VxPackageManager>> {
92        vec![]
93    }
94    /// Initialize the plugin (optional)
95    ///
96    /// This method is called when the plugin is loaded. Use it to perform
97    /// any necessary setup, such as checking dependencies or initializing
98    /// internal state.
99    async fn initialize(&mut self) -> Result<()> {
100        Ok(())
101    }
102
103    /// Shutdown the plugin (optional)
104    ///
105    /// This method is called when the plugin is being unloaded. Use it to
106    /// perform cleanup operations.
107    async fn shutdown(&mut self) -> Result<()> {
108        Ok(())
109    }
110
111    /// Check if this plugin supports a specific tool
112    ///
113    /// Default implementation checks all tools provided by this plugin,
114    /// including their aliases.
115    fn supports_tool(&self, tool_name: &str) -> bool {
116        self.tools()
117            .iter()
118            .any(|tool| tool.name() == tool_name || tool.aliases().contains(&tool_name))
119    }
120
121    /// Check if this plugin supports a specific package manager
122    ///
123    /// Default implementation checks all package managers provided by this plugin.
124    fn supports_package_manager(&self, pm_name: &str) -> bool {
125        self.package_managers()
126            .iter()
127            .any(|pm| pm.name() == pm_name)
128    }
129
130    /// Get a specific tool by name
131    ///
132    /// Returns the first tool that matches the given name or alias.
133    /// Note: This method returns an owned Box since the tools are owned by the plugin.
134    fn get_tool(&self, tool_name: &str) -> Option<Box<dyn VxTool>> {
135        self.tools()
136            .into_iter()
137            .find(|tool| tool.name() == tool_name || tool.aliases().contains(&tool_name))
138    }
139
140    /// Get a specific package manager by name
141    ///
142    /// Returns the first package manager that matches the given name.
143    /// Note: This method returns an owned Box since the package managers are owned by the plugin.
144    fn get_package_manager(&self, pm_name: &str) -> Option<Box<dyn VxPackageManager>> {
145        self.package_managers()
146            .into_iter()
147            .find(|pm| pm.name() == pm_name)
148    }
149    /// Check plugin compatibility with the current vx version
150    ///
151    /// Override this to implement version compatibility checks.
152    /// The default implementation accepts all versions.
153    fn is_compatible_with(&self, vx_version: &str) -> bool {
154        let _ = vx_version;
155        true
156    }
157
158    /// Get plugin dependencies
159    ///
160    /// Return a list of other plugins that this plugin depends on.
161    /// The default implementation has no dependencies.
162    fn dependencies(&self) -> Vec<&str> {
163        vec![]
164    }
165
166    /// Get plugin configuration schema
167    ///
168    /// Return a JSON schema describing the configuration options
169    /// that this plugin accepts. The default implementation has no configuration.
170    fn config_schema(&self) -> Option<serde_json::Value> {
171        None
172    }
173
174    /// Validate plugin configuration
175    ///
176    /// Check if the provided configuration is valid for this plugin.
177    /// The default implementation accepts any configuration.
178    fn validate_config(&self, _config: &serde_json::Value) -> Result<()> {
179        Ok(())
180    }
181
182    /// Additional metadata for the plugin (optional)
183    ///
184    /// Override this to provide plugin-specific metadata such as
185    /// supported platforms, feature flags, etc.
186    fn metadata(&self) -> HashMap<String, String> {
187        let mut metadata = HashMap::new();
188        metadata.insert("name".to_string(), self.name().to_string());
189        metadata.insert("version".to_string(), self.version().to_string());
190        metadata.insert("description".to_string(), self.description().to_string());
191
192        if let Some(author) = self.author() {
193            metadata.insert("author".to_string(), author.to_string());
194        }
195
196        if let Some(homepage) = self.homepage() {
197            metadata.insert("homepage".to_string(), homepage.to_string());
198        }
199
200        metadata
201    }
202}
203
204/// Standard plugin implementation for single-tool plugins
205///
206/// This is a convenience implementation for plugins that provide a single tool.
207/// It handles the boilerplate of implementing VxPlugin for simple cases.
208pub struct StandardPlugin {
209    name: String,
210    description: String,
211    version: String,
212    author: Option<String>,
213    homepage: Option<String>,
214    tool_factory: Box<dyn Fn() -> Box<dyn VxTool> + Send + Sync>,
215}
216
217impl StandardPlugin {
218    /// Create a new standard plugin
219    ///
220    /// # Arguments
221    ///
222    /// * `name` - Plugin name
223    /// * `description` - Plugin description
224    /// * `version` - Plugin version
225    /// * `tool_factory` - Factory function that creates the tool instance
226    pub fn new<F>(
227        name: impl Into<String>,
228        description: impl Into<String>,
229        version: impl Into<String>,
230        tool_factory: F,
231    ) -> Self
232    where
233        F: Fn() -> Box<dyn VxTool> + Send + Sync + 'static,
234    {
235        Self {
236            name: name.into(),
237            description: description.into(),
238            version: version.into(),
239            author: None,
240            homepage: None,
241            tool_factory: Box::new(tool_factory),
242        }
243    }
244
245    /// Set the plugin author
246    pub fn with_author(mut self, author: impl Into<String>) -> Self {
247        self.author = Some(author.into());
248        self
249    }
250
251    /// Set the plugin homepage
252    pub fn with_homepage(mut self, homepage: impl Into<String>) -> Self {
253        self.homepage = Some(homepage.into());
254        self
255    }
256}
257
258#[async_trait]
259impl VxPlugin for StandardPlugin {
260    fn name(&self) -> &str {
261        &self.name
262    }
263
264    fn description(&self) -> &str {
265        &self.description
266    }
267
268    fn version(&self) -> &str {
269        &self.version
270    }
271
272    fn author(&self) -> Option<&str> {
273        self.author.as_deref()
274    }
275
276    fn homepage(&self) -> Option<&str> {
277        self.homepage.as_deref()
278    }
279
280    fn tools(&self) -> Vec<Box<dyn VxTool>> {
281        vec![(self.tool_factory)()]
282    }
283}