1use semver::Version;
2use serde_json::to_string_pretty;
3use std::collections::HashMap;
4use std::fs;
5use std::sync::Arc;
6use thiserror::Error;
7use tokio::sync::RwLock;
8
9use crate::VanguardPlugin;
10
11#[derive(Debug, Clone, PartialEq)]
13pub enum PluginState {
14 Active,
16 Inactive,
18 Failed(String),
20}
21
22#[derive(Error, Debug)]
24pub enum RegistryError {
25 #[error("Plugin not found: {0}")]
27 NotFound(String),
28
29 #[error("Version conflict: {plugin} {version} conflicts with existing version")]
31 VersionConflict {
32 plugin: String,
34 version: String,
36 },
37
38 #[error("Invalid version: {0}")]
40 InvalidVersion(String),
41
42 #[error("Plugin already registered: {plugin} {version}")]
44 AlreadyRegistered {
45 plugin: String,
47 version: String,
49 },
50}
51
52#[derive(Debug)]
54struct VersionedPlugin {
55 version: Version,
57 plugin: Arc<dyn VanguardPlugin>,
59 state: PluginState,
61}
62
63#[derive(Debug)]
68pub struct PluginRegistry {
69 plugins: RwLock<HashMap<String, Vec<VersionedPlugin>>>,
71 active_versions: RwLock<HashMap<String, Version>>,
73}
74
75impl Default for PluginRegistry {
76 fn default() -> Self {
77 Self::new()
78 }
79}
80
81impl PluginRegistry {
82 pub fn new() -> Self {
84 Self {
85 plugins: RwLock::new(HashMap::new()),
86 active_versions: RwLock::new(HashMap::new()),
87 }
88 }
89
90 pub async fn register_plugin(
92 &self,
93 plugin: Arc<dyn VanguardPlugin>,
94 ) -> Result<(), RegistryError> {
95 let name = plugin.metadata().name.clone();
96 let version_str = plugin.metadata().version.clone();
97 let version = Version::parse(&version_str)
98 .map_err(|_| RegistryError::InvalidVersion(version_str.clone()))?;
99
100 let mut plugins = self.plugins.write().await;
101
102 let versions = plugins.entry(name.clone()).or_insert_with(Vec::new);
104
105 if versions.iter().any(|v| v.version == version) {
107 return Err(RegistryError::AlreadyRegistered {
108 plugin: name,
109 version: version_str,
110 });
111 }
112
113 versions.push(VersionedPlugin {
115 version: version.clone(),
116 plugin,
117 state: PluginState::Active,
118 });
119
120 versions.sort_by(|a, b| b.version.cmp(&a.version));
122
123 let mut active_versions = self.active_versions.write().await;
125 let latest_version = &versions[0].version;
126 active_versions.insert(name, latest_version.clone());
127
128 Ok(())
129 }
130
131 pub async fn get_active_version(
133 &self,
134 name: &str,
135 ) -> Result<Arc<dyn VanguardPlugin>, RegistryError> {
136 let active_versions = self.active_versions.read().await;
137 let plugins = self.plugins.read().await;
138
139 let version = active_versions
140 .get(name)
141 .ok_or_else(|| RegistryError::NotFound(name.to_string()))?;
142
143 let versions = plugins
144 .get(name)
145 .ok_or_else(|| RegistryError::NotFound(name.to_string()))?;
146
147 let plugin = versions
148 .iter()
149 .find(|v| v.version == *version)
150 .ok_or_else(|| RegistryError::NotFound(name.to_string()))?;
151
152 Ok(plugin.plugin.clone())
153 }
154
155 pub async fn get_plugin(&self, name: &str) -> Option<Arc<dyn VanguardPlugin>> {
157 let plugins = self.plugins.read().await;
158 plugins
159 .get(name)
160 .and_then(|versions| versions.first().map(|v| v.plugin.clone()))
161 }
162
163 pub async fn activate_version(
165 &self,
166 name: &str,
167 version_str: &str,
168 ) -> Result<(), RegistryError> {
169 let version = Version::parse(version_str)
170 .map_err(|_| RegistryError::InvalidVersion(version_str.to_string()))?;
171
172 let plugins = self.plugins.read().await;
173
174 let versions = plugins
176 .get(name)
177 .ok_or_else(|| RegistryError::NotFound(name.to_string()))?;
178
179 if !versions.iter().any(|v| v.version == version) {
180 return Err(RegistryError::NotFound(format!("{} {}", name, version_str)));
181 }
182
183 let mut active_versions = self.active_versions.write().await;
185 active_versions.insert(name.to_string(), version);
186
187 Ok(())
188 }
189
190 pub async fn get_plugin_state(
192 &self,
193 name: &str,
194 version_str: &str,
195 ) -> Result<PluginState, RegistryError> {
196 let version = Version::parse(version_str)
197 .map_err(|_| RegistryError::InvalidVersion(version_str.to_string()))?;
198
199 let plugins = self.plugins.read().await;
200
201 let versions = plugins
202 .get(name)
203 .ok_or_else(|| RegistryError::NotFound(name.to_string()))?;
204
205 let plugin = versions
206 .iter()
207 .find(|v| v.version == version)
208 .ok_or_else(|| RegistryError::NotFound(format!("{} {}", name, version_str)))?;
209
210 Ok(plugin.state.clone())
211 }
212
213 pub async fn set_plugin_state(
215 &self,
216 name: &str,
217 version_str: &str,
218 state: PluginState,
219 ) -> Result<(), RegistryError> {
220 let version = Version::parse(version_str)
221 .map_err(|_| RegistryError::InvalidVersion(version_str.to_string()))?;
222
223 let mut plugins = self.plugins.write().await;
224
225 let versions = plugins
226 .get_mut(name)
227 .ok_or_else(|| RegistryError::NotFound(name.to_string()))?;
228
229 let plugin = versions
230 .iter_mut()
231 .find(|v| v.version == version)
232 .ok_or_else(|| RegistryError::NotFound(format!("{} {}", name, version_str)))?;
233
234 plugin.state = state;
235 Ok(())
236 }
237
238 pub async fn remove_plugin(&self, name: &str, version_str: &str) -> Result<(), RegistryError> {
240 let version = Version::parse(version_str)
241 .map_err(|_| RegistryError::InvalidVersion(version_str.to_string()))?;
242
243 let mut plugins = self.plugins.write().await;
244
245 let versions = plugins
246 .get_mut(name)
247 .ok_or_else(|| RegistryError::NotFound(name.to_string()))?;
248
249 let index = versions
251 .iter()
252 .position(|v| v.version == version)
253 .ok_or_else(|| RegistryError::NotFound(format!("{} {}", name, version_str)))?;
254
255 versions.remove(index);
256
257 if versions.is_empty() {
259 plugins.remove(name);
260
261 let mut active_versions = self.active_versions.write().await;
263 active_versions.remove(name);
264 }
265
266 Ok(())
267 }
268
269 pub async fn save_plugin_info(
271 &self,
272 plugin_info: &crate::PluginInfo,
273 ) -> Result<(), RegistryError> {
274 let home_dir = dirs::home_dir().ok_or_else(|| {
276 RegistryError::NotFound("Could not determine home directory".to_string())
277 })?;
278 let registry_dir = home_dir.join(".vanguard").join("registry");
279 fs::create_dir_all(®istry_dir).map_err(|e| {
280 RegistryError::NotFound(format!("Failed to create registry directory: {}", e))
281 })?;
282
283 let json = to_string_pretty(plugin_info).map_err(|e| {
285 RegistryError::NotFound(format!("Failed to serialize plugin info: {}", e))
286 })?;
287
288 let plugin_info_path = registry_dir.join(format!("{}.json", plugin_info.name));
290 fs::write(&plugin_info_path, json).map_err(|e| {
291 RegistryError::NotFound(format!("Failed to write plugin info file: {}", e))
292 })?;
293
294 Ok(())
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301 use crate::{PluginMetadata, ValidationResult};
302 use async_trait::async_trait;
303
304 #[derive(Debug)]
306 struct TestPlugin {
307 metadata: PluginMetadata,
308 }
309
310 #[async_trait]
311 impl VanguardPlugin for TestPlugin {
312 fn metadata(&self) -> &PluginMetadata {
313 &self.metadata
314 }
315
316 async fn validate(&self) -> ValidationResult {
317 ValidationResult::Passed
318 }
319
320 async fn initialize(&self) -> Result<(), String> {
321 Ok(())
322 }
323
324 async fn cleanup(&self) -> Result<(), String> {
325 Ok(())
326 }
327 }
328
329 fn create_test_plugin(name: &str, version: &str) -> Arc<dyn VanguardPlugin> {
330 Arc::new(TestPlugin {
331 metadata: PluginMetadata {
332 name: name.to_string(),
333 version: version.to_string(),
334 description: "Test Plugin".to_string(),
335 author: "Test Author".to_string(),
336 min_vanguard_version: Some("0.1.0".to_string()),
337 max_vanguard_version: Some("2.0.0".to_string()),
338 dependencies: vec![],
339 },
340 })
341 }
342
343 #[tokio::test]
344 async fn test_register_plugin() {
345 let registry = PluginRegistry::new();
346 let plugin = create_test_plugin("test-plugin", "1.0.0");
347
348 assert!(registry.register_plugin(plugin.clone()).await.is_ok());
349
350 let registered = registry.get_plugin("test-plugin").await;
352 assert!(registered.is_some());
353
354 let duplicate = create_test_plugin("test-plugin", "1.0.0");
356 assert!(matches!(
357 registry.register_plugin(duplicate).await,
358 Err(RegistryError::AlreadyRegistered { .. })
359 ));
360 }
361
362 #[tokio::test]
363 async fn test_version_management() {
364 let registry = PluginRegistry::new();
365
366 let v1 = create_test_plugin("test-plugin", "1.0.0");
368 let v2 = create_test_plugin("test-plugin", "1.1.0");
369
370 registry.register_plugin(v1).await.unwrap();
371 registry.register_plugin(v2).await.unwrap();
372
373 let active = registry.get_active_version("test-plugin").await.unwrap();
375 assert_eq!(active.metadata().version, "1.1.0");
376
377 registry
379 .activate_version("test-plugin", "1.0.0")
380 .await
381 .unwrap();
382 let active = registry.get_active_version("test-plugin").await.unwrap();
383 assert_eq!(active.metadata().version, "1.0.0");
384 }
385
386 #[tokio::test]
387 async fn test_plugin_state() {
388 let registry = PluginRegistry::new();
389 let plugin = create_test_plugin("test-plugin", "1.0.0");
390
391 registry.register_plugin(plugin).await.unwrap();
392
393 let state = registry
395 .get_plugin_state("test-plugin", "1.0.0")
396 .await
397 .unwrap();
398 assert_eq!(state, PluginState::Active);
399
400 registry
402 .set_plugin_state("test-plugin", "1.0.0", PluginState::Inactive)
403 .await
404 .unwrap();
405 let state = registry
406 .get_plugin_state("test-plugin", "1.0.0")
407 .await
408 .unwrap();
409 assert_eq!(state, PluginState::Inactive);
410 }
411
412 #[tokio::test]
413 async fn test_plugin_removal() {
414 let registry = PluginRegistry::new();
415 let plugin = create_test_plugin("test-plugin", "1.0.0");
416
417 registry.register_plugin(plugin).await.unwrap();
418 assert!(registry.remove_plugin("test-plugin", "1.0.0").await.is_ok());
419
420 assert!(registry.get_plugin("test-plugin").await.is_none());
422
423 assert!(matches!(
425 registry.remove_plugin("non-existent", "1.0.0").await,
426 Err(RegistryError::NotFound(_))
427 ));
428 }
429}