vtcode_core/marketplace/
config.rs1use 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#[derive(Debug, Clone, Deserialize, Serialize, Default)]
15pub struct MarketplaceSettings {
16 #[serde(default)]
18 pub marketplaces: Vec<MarketplaceSource>,
19
20 #[serde(default)]
22 pub installed_plugins: Vec<InstalledPlugin>,
23
24 #[serde(default)]
26 pub auto_update: AutoUpdateSettings,
27
28 #[serde(default)]
30 pub security: SecuritySettings,
31}
32
33#[derive(Debug, Clone, Deserialize, Serialize)]
35pub struct InstalledPlugin {
36 pub id: String,
38
39 pub name: String,
41
42 pub version: String,
44
45 pub source: String,
47
48 pub install_path: PathBuf,
50
51 pub enabled: bool,
53
54 pub trust_level: crate::config::PluginTrustLevel,
56
57 pub installed_at: String, }
60
61#[derive(Debug, Clone, Deserialize, Serialize)]
63pub struct AutoUpdateSettings {
64 #[serde(default = "default_true")]
66 pub marketplaces: bool,
67
68 #[serde(default = "default_true")]
70 pub plugins: bool,
71
72 #[serde(default = "default_update_interval")]
74 pub check_interval_hours: u32,
75}
76
77#[derive(Debug, Clone, Deserialize, Serialize)]
79pub struct SecuritySettings {
80 #[serde(default)]
82 pub default_trust_level: crate::config::PluginTrustLevel,
83
84 #[serde(default = "default_true")]
86 pub require_confirmation: bool,
87
88 #[serde(default)]
90 pub allowed_sources: Vec<String>,
91
92 #[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 pub async fn load_from_file(config_path: &Path) -> Result<Self> {
129 if !config_path.exists() {
130 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).with_context(|| {
137 format!(
138 "Failed to parse marketplace config file: {}",
139 config_path.display()
140 )
141 })?;
142
143 Ok(settings)
144 }
145
146 pub async fn save_to_file(&self, config_path: &Path) -> Result<()> {
148 let content =
149 toml::to_string(&self).with_context(|| "Failed to serialize marketplace settings")?;
150
151 write_file_with_context(config_path, &content, "marketplace config file").await?;
152
153 Ok(())
154 }
155
156 pub fn add_marketplace(&mut self, marketplace: MarketplaceSource) {
158 if !self.marketplaces.iter().any(|m| m.id() == marketplace.id()) {
160 self.marketplaces.push(marketplace);
161 }
162 }
163
164 pub fn remove_marketplace(&mut self, id: &str) -> bool {
166 let initial_len = self.marketplaces.len();
167 self.marketplaces.retain(|m| m.id() != id);
168 self.marketplaces.len() != initial_len
169 }
170
171 pub fn is_source_allowed(&self, source_url: &str) -> bool {
173 let allowed = self.security.allowed_sources.is_empty()
175 || self
176 .security
177 .allowed_sources
178 .iter()
179 .any(|s| source_url.contains(s));
180
181 let blocked = self
183 .security
184 .blocked_sources
185 .iter()
186 .any(|s| source_url.contains(s));
187
188 allowed && !blocked
189 }
190
191 pub fn add_installed_plugin(&mut self, plugin: InstalledPlugin) {
193 match self
195 .installed_plugins
196 .iter_mut()
197 .find(|p| p.id == plugin.id)
198 {
199 Some(existing) => {
200 existing.name = plugin.name;
202 existing.version = plugin.version;
203 existing.source = plugin.source;
204 existing.install_path = plugin.install_path;
205 existing.enabled = plugin.enabled;
206 existing.trust_level = plugin.trust_level;
207 existing.installed_at = plugin.installed_at;
208 }
209 None => {
210 self.installed_plugins.push(plugin);
211 }
212 }
213 }
214
215 pub fn remove_installed_plugin(&mut self, plugin_id: &str) -> bool {
217 let initial_len = self.installed_plugins.len();
218 self.installed_plugins.retain(|p| p.id != plugin_id);
219 self.installed_plugins.len() != initial_len
220 }
221
222 pub fn get_installed_plugin(&self, plugin_id: &str) -> Option<&InstalledPlugin> {
224 self.installed_plugins.iter().find(|p| p.id == plugin_id)
225 }
226
227 pub fn enable_plugin(&mut self, plugin_id: &str) -> Result<()> {
229 match self
230 .installed_plugins
231 .iter_mut()
232 .find(|p| p.id == plugin_id)
233 {
234 Some(plugin) => {
235 plugin.enabled = true;
236 Ok(())
237 }
238 None => bail!("Plugin '{}' not found in installed plugins", plugin_id),
239 }
240 }
241
242 pub fn disable_plugin(&mut self, plugin_id: &str) -> Result<()> {
244 match self
245 .installed_plugins
246 .iter_mut()
247 .find(|p| p.id == plugin_id)
248 {
249 Some(plugin) => {
250 plugin.enabled = false;
251 Ok(())
252 }
253 None => bail!("Plugin '{}' not found in installed plugins", plugin_id),
254 }
255 }
256}
257
258pub fn get_marketplace_config_path(base_config_dir: &Path) -> PathBuf {
260 base_config_dir.join("marketplace.toml")
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[tokio::test]
268 async fn test_marketplace_settings_default() {
269 let settings = MarketplaceSettings::default();
270 assert!(settings.marketplaces.is_empty());
271 assert!(settings.installed_plugins.is_empty());
272 assert!(settings.auto_update.marketplaces);
273 assert_eq!(settings.auto_update.check_interval_hours, 24);
274 assert_eq!(
275 settings.security.default_trust_level,
276 crate::config::PluginTrustLevel::Sandbox
277 );
278 }
279
280 #[tokio::test]
281 async fn test_marketplace_settings_save_load() {
282 let temp_dir = tempfile::tempdir().unwrap();
283 let config_path = temp_dir.path().join("test_marketplace.toml");
284
285 let mut settings = MarketplaceSettings::default();
286 settings.auto_update.marketplaces = false;
287 settings.security.default_trust_level = crate::config::PluginTrustLevel::Trusted;
288
289 settings.save_to_file(&config_path).await.unwrap();
290
291 let loaded_settings = MarketplaceSettings::load_from_file(&config_path)
292 .await
293 .unwrap();
294 assert_eq!(
295 settings.auto_update.marketplaces,
296 loaded_settings.auto_update.marketplaces
297 );
298 assert_eq!(
299 settings.security.default_trust_level,
300 loaded_settings.security.default_trust_level
301 );
302 }
303
304 #[tokio::test]
305 async fn test_add_remove_marketplace() {
306 let mut settings = MarketplaceSettings::default();
307
308 let marketplace = MarketplaceSource::Git {
309 id: "test".to_string(),
310 url: "https://example.com/test".to_string(),
311 refspec: None,
312 };
313
314 settings.add_marketplace(marketplace.clone());
315 assert_eq!(settings.marketplaces.len(), 1);
316
317 settings.remove_marketplace("test");
318 assert_eq!(settings.marketplaces.len(), 0);
319 }
320
321 #[tokio::test]
322 async fn test_plugin_enable_disable() {
323 let mut settings = MarketplaceSettings::default();
324
325 let plugin = InstalledPlugin {
326 id: "test-plugin".to_string(),
327 name: "Test Plugin".to_string(),
328 version: "1.0.0".to_string(),
329 source: "test-marketplace".to_string(),
330 install_path: PathBuf::from("/tmp/test"),
331 enabled: false,
332 trust_level: crate::config::PluginTrustLevel::Sandbox,
333 installed_at: "2023-01-01".to_string(),
334 };
335
336 settings.add_installed_plugin(plugin);
337
338 settings.enable_plugin("test-plugin").unwrap();
339 assert!(
340 settings
341 .get_installed_plugin("test-plugin")
342 .unwrap()
343 .enabled
344 );
345
346 settings.disable_plugin("test-plugin").unwrap();
347 assert!(
348 !settings
349 .get_installed_plugin("test-plugin")
350 .unwrap()
351 .enabled
352 );
353 }
354}