Skip to main content

vtcode_core/marketplace/
config.rs

1//! Marketplace configuration system for VT Code
2//!
3//! This module handles the integration of marketplace settings with VT Code's configuration system.
4
5use std::path::{Path, PathBuf};
6
7use anyhow::{Context, Result, bail};
8use serde::{Deserialize, Serialize};
9
10use crate::marketplace::MarketplaceSource;
11use crate::utils::file_utils::{read_file_with_context, write_file_with_context};
12
13/// Configuration for marketplace settings that integrates with VT Code's config system
14#[derive(Debug, Clone, Deserialize, Serialize, Default)]
15pub struct MarketplaceSettings {
16    /// List of configured marketplaces
17    #[serde(default)]
18    pub marketplaces: Vec<MarketplaceSource>,
19
20    /// List of installed plugins with their settings
21    #[serde(default)]
22    pub installed_plugins: Vec<InstalledPlugin>,
23
24    /// Auto-update settings
25    #[serde(default)]
26    pub auto_update: AutoUpdateSettings,
27
28    /// Security and trust settings
29    #[serde(default)]
30    pub security: SecuritySettings,
31}
32
33/// Information about an installed plugin
34#[derive(Debug, Clone, Deserialize, Serialize)]
35pub struct InstalledPlugin {
36    /// Plugin ID
37    pub id: String,
38
39    /// Name of the plugin
40    pub name: String,
41
42    /// Version of the plugin
43    pub version: String,
44
45    /// Source marketplace
46    pub source: String,
47
48    /// Installation path
49    pub install_path: PathBuf,
50
51    /// Whether the plugin is enabled
52    pub enabled: bool,
53
54    /// Trust level of the plugin
55    pub trust_level: crate::config::PluginTrustLevel,
56
57    /// Installation timestamp
58    pub installed_at: String, // Using string for simplicity, could be a proper datetime type
59}
60
61/// Auto-update settings for marketplaces and plugins
62#[derive(Debug, Clone, Deserialize, Serialize)]
63pub struct AutoUpdateSettings {
64    /// Whether to auto-update marketplaces
65    #[serde(default = "default_true")]
66    pub marketplaces: bool,
67
68    /// Whether to auto-update plugins
69    #[serde(default = "default_true")]
70    pub plugins: bool,
71
72    /// Check for updates interval in hours
73    #[serde(default = "default_update_interval")]
74    pub check_interval_hours: u32,
75}
76
77/// Security and trust settings
78#[derive(Debug, Clone, Deserialize, Serialize)]
79pub struct SecuritySettings {
80    /// Default trust level for new plugins
81    #[serde(default)]
82    pub default_trust_level: crate::config::PluginTrustLevel,
83
84    /// Whether to require confirmation for untrusted plugins
85    #[serde(default = "default_true")]
86    pub require_confirmation: bool,
87
88    /// List of allowed plugin sources (whitelist)
89    #[serde(default)]
90    pub allowed_sources: Vec<String>,
91
92    /// List of blocked plugin sources (blacklist)
93    #[serde(default)]
94    pub blocked_sources: Vec<String>,
95}
96
97impl Default for AutoUpdateSettings {
98    fn default() -> Self {
99        Self {
100            marketplaces: true,
101            plugins: true,
102            check_interval_hours: 24,
103        }
104    }
105}
106
107impl Default for SecuritySettings {
108    fn default() -> Self {
109        Self {
110            default_trust_level: crate::config::PluginTrustLevel::Sandbox,
111            require_confirmation: true,
112            allowed_sources: Vec::new(),
113            blocked_sources: Vec::new(),
114        }
115    }
116}
117
118fn default_true() -> bool {
119    true
120}
121
122fn default_update_interval() -> u32 {
123    24
124}
125
126impl MarketplaceSettings {
127    /// Load marketplace settings from a configuration file
128    pub async fn load_from_file(config_path: &Path) -> Result<Self> {
129        if !tokio::fs::try_exists(config_path).await.unwrap_or(false) {
130            // Return default settings if file doesn't exist
131            return Ok(Self::default());
132        }
133
134        let content = read_file_with_context(config_path, "marketplace config file").await?;
135
136        let settings: MarketplaceSettings = toml::from_str(&content)
137            .with_context(|| format!("Failed to parse marketplace config file: {}", config_path.display()))?;
138
139        Ok(settings)
140    }
141
142    /// Save marketplace settings to a configuration file
143    pub async fn save_to_file(&self, config_path: &Path) -> Result<()> {
144        let content = toml::to_string(&self).with_context(|| "Failed to serialize marketplace settings")?;
145
146        write_file_with_context(config_path, &content, "marketplace config file").await?;
147
148        Ok(())
149    }
150
151    /// Add a marketplace to the configuration
152    pub fn add_marketplace(&mut self, marketplace: MarketplaceSource) {
153        // Check if marketplace already exists
154        if !self.marketplaces.iter().any(|m| m.id() == marketplace.id()) {
155            self.marketplaces.push(marketplace);
156        }
157    }
158
159    /// Remove a marketplace from the configuration
160    pub fn remove_marketplace(&mut self, id: &str) -> bool {
161        let initial_len = self.marketplaces.len();
162        self.marketplaces.retain(|m| m.id() != id);
163        self.marketplaces.len() != initial_len
164    }
165
166    /// Add an installed plugin to the configuration
167    pub fn add_installed_plugin(&mut self, plugin: InstalledPlugin) {
168        // Check if plugin already exists and update it, or add as new
169        match self.installed_plugins.iter_mut().find(|p| p.id == plugin.id) {
170            Some(existing) => {
171                // Update existing plugin info
172                existing.name = plugin.name;
173                existing.version = plugin.version;
174                existing.source = plugin.source;
175                existing.install_path = plugin.install_path;
176                existing.enabled = plugin.enabled;
177                existing.trust_level = plugin.trust_level;
178                existing.installed_at = plugin.installed_at;
179            }
180            None => {
181                self.installed_plugins.push(plugin);
182            }
183        }
184    }
185
186    /// Get an installed plugin by ID
187    pub fn get_installed_plugin(&self, plugin_id: &str) -> Option<&InstalledPlugin> {
188        self.installed_plugins.iter().find(|p| p.id == plugin_id)
189    }
190
191    /// Enable a plugin
192    pub fn enable_plugin(&mut self, plugin_id: &str) -> Result<()> {
193        match self.installed_plugins.iter_mut().find(|p| p.id == plugin_id) {
194            Some(plugin) => {
195                plugin.enabled = true;
196                Ok(())
197            }
198            None => bail!("Plugin '{plugin_id}' not found in installed plugins"),
199        }
200    }
201
202    /// Disable a plugin
203    pub fn disable_plugin(&mut self, plugin_id: &str) -> Result<()> {
204        match self.installed_plugins.iter_mut().find(|p| p.id == plugin_id) {
205            Some(plugin) => {
206                plugin.enabled = false;
207                Ok(())
208            }
209            None => bail!("Plugin '{plugin_id}' not found in installed plugins"),
210        }
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217
218    #[tokio::test]
219    async fn test_marketplace_settings_default() {
220        let settings = MarketplaceSettings::default();
221        assert!(settings.marketplaces.is_empty());
222        assert!(settings.installed_plugins.is_empty());
223        assert!(settings.auto_update.marketplaces);
224        assert_eq!(settings.auto_update.check_interval_hours, 24);
225        assert_eq!(settings.security.default_trust_level, crate::config::PluginTrustLevel::Sandbox);
226    }
227
228    #[tokio::test]
229    async fn test_marketplace_settings_save_load() {
230        let temp_dir = tempfile::tempdir().unwrap();
231        let config_path = temp_dir.path().join("test_marketplace.toml");
232
233        let mut settings = MarketplaceSettings::default();
234        settings.auto_update.marketplaces = false;
235        settings.security.default_trust_level = crate::config::PluginTrustLevel::Trusted;
236
237        settings.save_to_file(&config_path).await.unwrap();
238
239        let loaded_settings = MarketplaceSettings::load_from_file(&config_path).await.unwrap();
240        assert_eq!(settings.auto_update.marketplaces, loaded_settings.auto_update.marketplaces);
241        assert_eq!(settings.security.default_trust_level, loaded_settings.security.default_trust_level);
242    }
243
244    #[tokio::test]
245    async fn test_add_remove_marketplace() {
246        let mut settings = MarketplaceSettings::default();
247
248        let marketplace = MarketplaceSource::Git {
249            id: "test".to_string(),
250            url: "https://example.com/test".to_string(),
251            refspec: None,
252        };
253
254        settings.add_marketplace(marketplace.clone());
255        assert_eq!(settings.marketplaces.len(), 1);
256
257        settings.remove_marketplace("test");
258        assert_eq!(settings.marketplaces.len(), 0);
259    }
260
261    #[tokio::test]
262    async fn test_plugin_enable_disable() {
263        let mut settings = MarketplaceSettings::default();
264
265        let plugin = InstalledPlugin {
266            id: "test-plugin".to_string(),
267            name: "Test Plugin".to_string(),
268            version: "1.0.0".to_string(),
269            source: "test-marketplace".to_string(),
270            install_path: PathBuf::from("/tmp/test"),
271            enabled: false,
272            trust_level: crate::config::PluginTrustLevel::Sandbox,
273            installed_at: "2023-01-01".to_string(),
274        };
275
276        settings.add_installed_plugin(plugin);
277
278        settings.enable_plugin("test-plugin").unwrap();
279        assert!(settings.get_installed_plugin("test-plugin").unwrap().enabled);
280
281        settings.disable_plugin("test-plugin").unwrap();
282        assert!(!settings.get_installed_plugin("test-plugin").unwrap().enabled);
283    }
284}