mockforge_plugin_registry/
lib.rs

1//! MockForge Plugin Registry
2//!
3//! Central registry for discovering, publishing, and installing plugins.
4
5pub mod api;
6pub mod config;
7pub mod dependencies;
8pub mod hot_reload;
9pub mod index;
10pub mod manifest;
11pub mod reviews;
12pub mod runtime;
13pub mod security;
14pub mod storage;
15
16use serde::{Deserialize, Serialize};
17use std::collections::HashMap;
18use thiserror::Error;
19
20/// Registry errors
21#[derive(Error, Debug)]
22pub enum RegistryError {
23    #[error("Plugin not found: {0}")]
24    PluginNotFound(String),
25
26    #[error("Invalid version: {0}")]
27    InvalidVersion(String),
28
29    #[error("Plugin already exists: {0}")]
30    PluginExists(String),
31
32    #[error("Authentication required")]
33    AuthRequired,
34
35    #[error("Permission denied")]
36    PermissionDenied,
37
38    #[error("Invalid manifest: {0}")]
39    InvalidManifest(String),
40
41    #[error("Storage error: {0}")]
42    Storage(String),
43
44    #[error("Network error: {0}")]
45    Network(String),
46
47    #[error(transparent)]
48    Io(#[from] std::io::Error),
49
50    #[error(transparent)]
51    Serde(#[from] serde_json::Error),
52}
53
54pub type Result<T> = std::result::Result<T, RegistryError>;
55
56/// Plugin registry entry
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct RegistryEntry {
59    /// Plugin name
60    pub name: String,
61
62    /// Plugin description
63    pub description: String,
64
65    /// Current version
66    pub version: String,
67
68    /// All available versions
69    pub versions: Vec<VersionEntry>,
70
71    /// Author information
72    pub author: AuthorInfo,
73
74    /// Plugin tags
75    pub tags: Vec<String>,
76
77    /// Plugin category
78    pub category: PluginCategory,
79
80    /// Download count
81    pub downloads: u64,
82
83    /// Rating (0.0 - 5.0)
84    pub rating: f64,
85
86    /// Total reviews
87    pub reviews_count: u32,
88
89    /// Repository URL
90    pub repository: Option<String>,
91
92    /// Homepage URL
93    pub homepage: Option<String>,
94
95    /// License
96    pub license: String,
97
98    /// Created timestamp
99    pub created_at: String,
100
101    /// Updated timestamp
102    pub updated_at: String,
103}
104
105/// Version-specific entry
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct VersionEntry {
108    /// Version string (semver)
109    pub version: String,
110
111    /// Download URL
112    pub download_url: String,
113
114    /// SHA-256 checksum
115    pub checksum: String,
116
117    /// File size in bytes
118    pub size: u64,
119
120    /// Published timestamp
121    pub published_at: String,
122
123    /// Yanked (removed from index)
124    pub yanked: bool,
125
126    /// Minimum MockForge version required
127    pub min_mockforge_version: Option<String>,
128
129    /// Dependencies
130    pub dependencies: HashMap<String, String>,
131}
132
133/// Author information
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct AuthorInfo {
136    pub name: String,
137    pub email: Option<String>,
138    pub url: Option<String>,
139}
140
141/// Plugin category
142#[derive(Debug, Clone, Serialize, Deserialize)]
143#[serde(rename_all = "lowercase")]
144pub enum PluginCategory {
145    Auth,
146    Template,
147    Response,
148    DataSource,
149    Middleware,
150    Testing,
151    Observability,
152    Other,
153}
154
155/// Search query
156#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct SearchQuery {
158    /// Search terms
159    pub query: Option<String>,
160
161    /// Filter by category
162    pub category: Option<PluginCategory>,
163
164    /// Filter by tags
165    pub tags: Vec<String>,
166
167    /// Sort order
168    pub sort: SortOrder,
169
170    /// Page number (0-indexed)
171    pub page: usize,
172
173    /// Results per page
174    pub per_page: usize,
175}
176
177impl Default for SearchQuery {
178    fn default() -> Self {
179        Self {
180            query: None,
181            category: None,
182            tags: vec![],
183            sort: SortOrder::Relevance,
184            page: 0,
185            per_page: 20,
186        }
187    }
188}
189
190/// Sort order
191#[derive(Debug, Clone, Serialize, Deserialize)]
192#[serde(rename_all = "lowercase")]
193pub enum SortOrder {
194    Relevance,
195    Downloads,
196    Rating,
197    Recent,
198    Name,
199}
200
201/// Search results
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct SearchResults {
204    pub plugins: Vec<RegistryEntry>,
205    pub total: usize,
206    pub page: usize,
207    pub per_page: usize,
208}
209
210/// Registry configuration
211#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct RegistryConfig {
213    /// Registry URL
214    pub url: String,
215
216    /// API token (optional)
217    pub token: Option<String>,
218
219    /// Cache directory
220    pub cache_dir: Option<String>,
221
222    /// Timeout in seconds
223    pub timeout: u64,
224
225    /// Alternative registries
226    pub alternative_registries: Vec<String>,
227}
228
229impl Default for RegistryConfig {
230    fn default() -> Self {
231        Self {
232            url: "https://registry.mockforge.dev".to_string(),
233            token: None,
234            cache_dir: None,
235            timeout: 30,
236            alternative_registries: vec![],
237        }
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244
245    #[test]
246    fn test_registry_entry_serialization() {
247        let entry = RegistryEntry {
248            name: "test-plugin".to_string(),
249            description: "Test plugin".to_string(),
250            version: "1.0.0".to_string(),
251            versions: vec![],
252            author: AuthorInfo {
253                name: "Test Author".to_string(),
254                email: Some("test@example.com".to_string()),
255                url: None,
256            },
257            tags: vec!["test".to_string()],
258            category: PluginCategory::Auth,
259            downloads: 100,
260            rating: 4.5,
261            reviews_count: 10,
262            repository: None,
263            homepage: None,
264            license: "MIT".to_string(),
265            created_at: "2025-01-01T00:00:00Z".to_string(),
266            updated_at: "2025-01-01T00:00:00Z".to_string(),
267        };
268
269        let json = serde_json::to_string(&entry).unwrap();
270        let deserialized: RegistryEntry = serde_json::from_str(&json).unwrap();
271        assert_eq!(entry.name, deserialized.name);
272    }
273
274    #[test]
275    fn test_search_query_default() {
276        let query = SearchQuery::default();
277        assert_eq!(query.page, 0);
278        assert_eq!(query.per_page, 20);
279    }
280}