sklears_core/plugin/discovery_marketplace.rs
1//! Plugin Discovery and Marketplace
2//!
3//! This module provides comprehensive plugin discovery and marketplace functionality
4//! for the sklears plugin system. It enables finding, installing, and managing
5//! plugins from remote repositories and community marketplaces.
6
7use super::core_traits::Plugin;
8use super::types_config::{PluginCapability, PluginCategory, PluginMetadata};
9use super::validation::{PluginManifest, PluginValidator, ValidationReport};
10use crate::error::{Result, SklearsError};
11use std::cmp::Ordering;
12use std::collections::HashMap;
13
14/// Plugin discovery service for remote repositories
15///
16/// The PluginDiscoveryService enables automatic discovery and installation of plugins
17/// from configured remote repositories. It provides caching, search functionality,
18/// and network-based plugin management.
19///
20/// # Features
21///
22/// - Multi-repository plugin discovery
23/// - Intelligent caching and index management
24/// - Advanced search capabilities with relevance scoring
25/// - Automatic plugin validation and installation
26/// - Network resilience and error handling
27///
28/// # Examples
29///
30/// ```rust,ignore
31/// use sklears_core::plugin::PluginDiscoveryService;
32///
33/// async fn discover_plugins() -> Result<(), Box<dyn std::error::Error>> {
34/// let discovery = PluginDiscoveryService::new();
35///
36/// // Discover all available plugins
37/// let plugins = discovery.discover_all().await?;
38/// println!("Found {} plugins", plugins.len());
39///
40/// // Search for specific plugins
41/// let query = SearchQuery {
42/// text: "linear regression".to_string(),
43/// category: Some(PluginCategory::Algorithm),
44/// limit: Some(10),
45/// };
46/// let results = discovery.search(&query).await?;
47///
48/// // Install a plugin
49/// if let Some(result) = results.first() {
50/// let install_result = discovery.install_plugin(&result.plugin_id, None).await?;
51/// println!("Installed plugin at: {}", install_result.install_path);
52/// }
53///
54/// Ok(())
55/// }
56/// ```
57#[derive(Debug)]
58pub struct PluginDiscoveryService {
59 /// Remote repositories for plugin discovery
60 repositories: Vec<PluginRepository>,
61 /// Local cache for repository data
62 cache: PluginCache,
63 /// Network client for remote operations
64 client: NetworkClient,
65 /// Search index for fast plugin lookups
66 search_index: SearchIndex,
67}
68
69impl PluginDiscoveryService {
70 /// Create a new discovery service
71 ///
72 /// Initializes the service with default official and community repositories.
73 /// Additional repositories can be added after creation.
74 ///
75 /// # Examples
76 ///
77 /// ```rust,ignore
78 /// use sklears_core::plugin::PluginDiscoveryService;
79 ///
80 /// let discovery = PluginDiscoveryService::new();
81 /// ```
82 pub fn new() -> Self {
83 Self {
84 repositories: vec![PluginRepository::official(), PluginRepository::community()],
85 cache: PluginCache::new(),
86 client: NetworkClient::new(),
87 search_index: SearchIndex::new(),
88 }
89 }
90
91 /// Add a custom repository
92 ///
93 /// Adds a new repository to the discovery service for plugin lookup.
94 ///
95 /// # Arguments
96 ///
97 /// * `repository` - The repository to add
98 ///
99 /// # Examples
100 ///
101 /// ```rust,ignore
102 /// use sklears_core::plugin::{PluginDiscoveryService, PluginRepository};
103 ///
104 /// let mut discovery = PluginDiscoveryService::new();
105 /// let custom_repo = PluginRepository {
106 /// name: "Company Internal".to_string(),
107 /// url: "https://internal.company.com/plugins".to_string(),
108 /// verified: true,
109 /// priority: 5,
110 /// };
111 /// discovery.add_repository(custom_repo);
112 /// ```
113 pub fn add_repository(&mut self, repository: PluginRepository) {
114 self.repositories.push(repository);
115 // Sort by priority (higher priority first)
116 self.repositories
117 .sort_by_key(|r| std::cmp::Reverse(r.priority));
118 }
119
120 /// Discover plugins from all repositories
121 ///
122 /// Scans all configured repositories for available plugins and returns
123 /// their manifests. Results are cached for improved performance.
124 ///
125 /// # Returns
126 ///
127 /// A vector of plugin manifests from all repositories, or an error if
128 /// no repositories are accessible.
129 ///
130 /// # Examples
131 ///
132 /// ```rust,ignore
133 /// use sklears_core::plugin::PluginDiscoveryService;
134 ///
135 /// async fn discover_all() -> Result<(), Box<dyn std::error::Error>> {
136 /// let discovery = PluginDiscoveryService::new();
137 /// let all_plugins = discovery.discover_all().await?;
138 ///
139 /// for manifest in &all_plugins {
140 /// println!("Found plugin: {} v{}",
141 /// manifest.metadata.name,
142 /// manifest.metadata.version);
143 /// }
144 /// Ok(())
145 /// }
146 /// ```
147 pub async fn discover_all(&self) -> Result<Vec<PluginManifest>> {
148 let mut all_plugins = Vec::new();
149 let mut discovered_from_any = false;
150
151 for repository in &self.repositories {
152 match self.discover_from_repository(repository).await {
153 Ok(mut plugins) => {
154 all_plugins.append(&mut plugins);
155 discovered_from_any = true;
156 }
157 Err(e) => {
158 eprintln!(
159 "Failed to discover from repository {}: {}",
160 repository.name, e
161 );
162 }
163 }
164 }
165
166 if !discovered_from_any && !self.repositories.is_empty() {
167 return Err(SklearsError::InvalidOperation(
168 "Failed to discover plugins from any repository".to_string(),
169 ));
170 }
171
172 // Remove duplicates based on plugin name and version
173 all_plugins.sort_by(|a, b| {
174 a.metadata
175 .name
176 .cmp(&b.metadata.name)
177 .then_with(|| a.metadata.version.cmp(&b.metadata.version))
178 });
179 all_plugins.dedup_by(|a, b| {
180 a.metadata.name == b.metadata.name && a.metadata.version == b.metadata.version
181 });
182
183 Ok(all_plugins)
184 }
185
186 /// Discover plugins from a specific repository
187 ///
188 /// Fetches plugins from a single repository, utilizing caching to avoid
189 /// unnecessary network requests.
190 ///
191 /// # Arguments
192 ///
193 /// * `repository` - The repository to query
194 ///
195 /// # Returns
196 ///
197 /// A vector of plugin manifests from the repository, or an error if
198 /// the repository is inaccessible.
199 pub async fn discover_from_repository(
200 &self,
201 repository: &PluginRepository,
202 ) -> Result<Vec<PluginManifest>> {
203 // Check cache first
204 if let Some(cached) = self.cache.get_repository_plugins(&repository.url) {
205 if !cached.is_expired() {
206 return Ok(cached.plugins);
207 }
208 }
209
210 // Fetch from remote
211 let plugins = self
212 .client
213 .fetch_repository_plugins(repository)
214 .await
215 .map_err(|e| {
216 SklearsError::InvalidOperation(format!(
217 "Failed to fetch plugins from {}: {}",
218 repository.name, e
219 ))
220 })?;
221
222 // Update cache
223 self.cache
224 .store_repository_plugins(&repository.url, &plugins);
225
226 // Update search index
227 self.search_index.index_plugins(&plugins);
228
229 Ok(plugins)
230 }
231
232 /// Search plugins by query
233 ///
234 /// Performs intelligent search across all known plugins using text matching,
235 /// category filtering, and relevance scoring. Combines local index results
236 /// with live repository searches for comprehensive results.
237 ///
238 /// # Arguments
239 ///
240 /// * `query` - The search query with filters and options
241 ///
242 /// # Returns
243 ///
244 /// A vector of search results sorted by relevance and popularity.
245 ///
246 /// # Examples
247 ///
248 /// ```rust,ignore
249 /// use sklears_core::plugin::{PluginDiscoveryService, SearchQuery, PluginCategory};
250 ///
251 /// async fn search_plugins() -> Result<(), Box<dyn std::error::Error>> {
252 /// let discovery = PluginDiscoveryService::new();
253 ///
254 /// let query = SearchQuery {
255 /// text: "classification".to_string(),
256 /// category: Some(PluginCategory::Algorithm),
257 /// capabilities: vec![PluginCapability::Parallel],
258 /// limit: Some(20),
259 /// min_rating: Some(4.0),
260 /// };
261 ///
262 /// let results = discovery.search(&query).await?;
263 /// for result in results {
264 /// println!("{}: {} (score: {:.2})",
265 /// result.plugin_id,
266 /// result.description,
267 /// result.relevance_score);
268 /// }
269 /// Ok(())
270 /// }
271 /// ```
272 pub async fn search(&self, query: &SearchQuery) -> Result<Vec<PluginSearchResult>> {
273 // First search local index
274 let mut results = self.search_index.search(query)?;
275
276 // If not enough results, search repositories
277 let target_count = query.limit.unwrap_or(10);
278 if results.len() < target_count {
279 for repository in &self.repositories {
280 if let Ok(remote_results) = self.client.search_repository(repository, query).await {
281 results.extend(remote_results);
282 if results.len() >= target_count * 2 {
283 break; // Enough results to sort and filter
284 }
285 }
286 }
287 }
288
289 // Apply filtering
290 if let Some(category) = &query.category {
291 results.retain(|r| r.category == *category);
292 }
293
294 if !query.capabilities.is_empty() {
295 results.retain(|r| {
296 query
297 .capabilities
298 .iter()
299 .all(|cap| r.capabilities.contains(cap))
300 });
301 }
302
303 if let Some(min_rating) = query.min_rating {
304 results.retain(|r| r.rating >= min_rating);
305 }
306
307 // Sort by relevance and popularity
308 results.sort_by(|a, b| {
309 b.relevance_score
310 .partial_cmp(&a.relevance_score)
311 .unwrap_or(Ordering::Equal)
312 .then_with(|| {
313 b.popularity_score
314 .partial_cmp(&a.popularity_score)
315 .unwrap_or(Ordering::Equal)
316 })
317 });
318
319 Ok(results.into_iter().take(target_count).collect())
320 }
321
322 /// Download and install plugin
323 ///
324 /// Downloads a plugin from repositories, validates it comprehensively,
325 /// and installs it locally for use.
326 ///
327 /// # Arguments
328 ///
329 /// * `plugin_id` - The unique identifier of the plugin to install
330 /// * `version` - Optional specific version to install (latest if None)
331 ///
332 /// # Returns
333 ///
334 /// Installation result with manifest, path, and validation report.
335 ///
336 /// # Errors
337 ///
338 /// Returns an error if the plugin is not found, validation fails,
339 /// or installation encounters issues.
340 ///
341 /// # Examples
342 ///
343 /// ```rust,ignore
344 /// use sklears_core::plugin::PluginDiscoveryService;
345 ///
346 /// async fn install_plugin() -> Result<(), Box<dyn std::error::Error>> {
347 /// let discovery = PluginDiscoveryService::new();
348 ///
349 /// let result = discovery.install_plugin("linear_regression", Some("2.1.0")).await?;
350 ///
351 /// println!("Plugin installed at: {}", result.install_path);
352 /// if !result.validation_report.warnings.is_empty() {
353 /// println!("Warnings: {:?}", result.validation_report.warnings);
354 /// }
355 /// Ok(())
356 /// }
357 /// ```
358 pub async fn install_plugin(
359 &self,
360 plugin_id: &str,
361 version: Option<&str>,
362 ) -> Result<PluginInstallResult> {
363 // Find plugin in repositories
364 let manifest = self.find_plugin_manifest(plugin_id, version).await?;
365
366 // Validate plugin comprehensively
367 let validator = PluginValidator::new();
368 let dummy_plugin = DummyPlugin::new();
369 let validation_report = validator.validate_comprehensive(&*dummy_plugin, &manifest)?;
370
371 if validation_report.has_errors() {
372 return Err(SklearsError::InvalidOperation(format!(
373 "Plugin validation failed: {} errors found",
374 validation_report.errors.len()
375 )));
376 }
377
378 // Download plugin
379 let plugin_data = self.client.download_plugin(&manifest).await.map_err(|e| {
380 SklearsError::InvalidOperation(format!("Failed to download plugin: {}", e))
381 })?;
382
383 // Verify download integrity
384 let computed_hash = self.compute_content_hash(&plugin_data);
385 if computed_hash != manifest.content_hash {
386 return Err(SklearsError::InvalidOperation(
387 "Plugin content hash verification failed".to_string(),
388 ));
389 }
390
391 // Install locally
392 let install_path = self.install_plugin_locally(&manifest, &plugin_data)?;
393
394 Ok(PluginInstallResult {
395 manifest,
396 install_path,
397 validation_report,
398 })
399 }
400
401 /// Find plugin manifest in repositories
402 ///
403 /// Searches through all configured repositories to find a plugin manifest
404 /// matching the specified ID and optional version.
405 async fn find_plugin_manifest(
406 &self,
407 plugin_id: &str,
408 version: Option<&str>,
409 ) -> Result<PluginManifest> {
410 let mut last_error = None;
411
412 for repository in &self.repositories {
413 match self
414 .client
415 .get_plugin_manifest(repository, plugin_id, version)
416 .await
417 {
418 Ok(manifest) => return Ok(manifest),
419 Err(e) => last_error = Some(e),
420 }
421 }
422
423 Err(SklearsError::InvalidOperation(format!(
424 "Plugin '{}' not found in any repository. Last error: {:?}",
425 plugin_id, last_error
426 )))
427 }
428
429 /// Install plugin locally
430 ///
431 /// Handles the local installation of a downloaded plugin, including
432 /// file extraction, permission setup, and registration preparation.
433 fn install_plugin_locally(&self, manifest: &PluginManifest, data: &[u8]) -> Result<String> {
434 // Create plugin directory
435 let plugin_dir = std::env::temp_dir()
436 .join("plugins")
437 .join(&manifest.metadata.name)
438 .display()
439 .to_string();
440 std::fs::create_dir_all(&plugin_dir).map_err(|e| {
441 SklearsError::InvalidOperation(format!("Failed to create plugin directory: {}", e))
442 })?;
443
444 // Write plugin data
445 let plugin_file = format!("{}/plugin.so", plugin_dir);
446 std::fs::write(&plugin_file, data).map_err(|e| {
447 SklearsError::InvalidOperation(format!("Failed to write plugin file: {}", e))
448 })?;
449
450 // Set appropriate permissions (readable and executable)
451 #[cfg(unix)]
452 {
453 use std::os::unix::fs::PermissionsExt;
454 std::fs::set_permissions(&plugin_file, std::fs::Permissions::from_mode(0o755))
455 .map_err(|e| {
456 SklearsError::InvalidOperation(format!("Failed to set permissions: {}", e))
457 })?;
458 }
459
460 // Write manifest for future reference
461 let manifest_file = format!("{}/manifest.json", plugin_dir);
462 let manifest_json = serde_json::to_string_pretty(manifest).map_err(|e| {
463 SklearsError::InvalidOperation(format!("Failed to serialize manifest: {}", e))
464 })?;
465 std::fs::write(manifest_file, manifest_json).map_err(|e| {
466 SklearsError::InvalidOperation(format!("Failed to write manifest: {}", e))
467 })?;
468
469 Ok(plugin_file)
470 }
471
472 /// Compute content hash for verification
473 fn compute_content_hash(&self, data: &[u8]) -> String {
474 // Simple hash implementation - in production, use SHA-256 or similar
475 use std::collections::hash_map::DefaultHasher;
476 use std::hash::{Hash, Hasher};
477
478 let mut hasher = DefaultHasher::new();
479 data.hash(&mut hasher);
480 format!("{:x}", hasher.finish())
481 }
482
483 /// Get repository statistics
484 ///
485 /// Returns statistics about configured repositories and their status.
486 pub async fn get_repository_stats(&self) -> Vec<RepositoryStats> {
487 let mut stats = Vec::new();
488
489 for repository in &self.repositories {
490 let plugin_count = match self.discover_from_repository(repository).await {
491 Ok(plugins) => plugins.len(),
492 Err(_) => 0,
493 };
494
495 stats.push(RepositoryStats {
496 name: repository.name.clone(),
497 url: repository.url.clone(),
498 plugin_count,
499 verified: repository.verified,
500 last_updated: std::time::SystemTime::now(), // Placeholder
501 });
502 }
503
504 stats
505 }
506}
507
508impl Default for PluginDiscoveryService {
509 fn default() -> Self {
510 Self::new()
511 }
512}
513
514/// Community plugin marketplace
515///
516/// The PluginMarketplace provides a comprehensive platform for plugin discovery,
517/// rating, reviewing, and analytics. It combines the discovery service with
518/// community features to create a full marketplace experience.
519///
520/// # Features
521///
522/// - Featured plugin recommendations
523/// - Community ratings and reviews
524/// - Download tracking and analytics
525/// - Plugin popularity scoring
526/// - Trend analysis and reporting
527///
528/// # Examples
529///
530/// ```rust,ignore
531/// use sklears_core::plugin::PluginMarketplace;
532///
533/// async fn marketplace_demo() -> Result<(), Box<dyn std::error::Error>> {
534/// let marketplace = PluginMarketplace::new();
535///
536/// // Get featured plugins
537/// let featured = marketplace.get_featured_plugins().await?;
538/// println!("Featured plugins: {}", featured.len());
539///
540/// // Rate a plugin
541/// marketplace.rate_plugin("linear_regression", "user123", 4.5).await?;
542///
543/// // Get plugin statistics
544/// let stats = marketplace.get_plugin_stats("linear_regression").await?;
545/// println!("Average rating: {:.1}", stats.average_rating);
546///
547/// Ok(())
548/// }
549/// ```
550#[derive(Debug)]
551pub struct PluginMarketplace {
552 /// Discovery service for plugin management
553 discovery: PluginDiscoveryService,
554 /// Rating system for community feedback
555 rating_system: RatingSystem,
556 /// Review system for detailed feedback
557 review_system: ReviewSystem,
558 /// Download tracking for popularity metrics
559 download_tracker: DownloadTracker,
560 /// Analytics engine for trend analysis
561 analytics: PluginAnalytics,
562}
563
564impl PluginMarketplace {
565 /// Create a new marketplace
566 ///
567 /// Initializes all marketplace components including discovery, ratings,
568 /// reviews, and analytics systems.
569 ///
570 /// # Examples
571 ///
572 /// ```rust
573 /// use sklears_core::plugin::PluginMarketplace;
574 ///
575 /// let marketplace = PluginMarketplace::new();
576 /// ```
577 pub fn new() -> Self {
578 Self {
579 discovery: PluginDiscoveryService::new(),
580 rating_system: RatingSystem::new(),
581 review_system: ReviewSystem::new(),
582 download_tracker: DownloadTracker::new(),
583 analytics: PluginAnalytics::new(),
584 }
585 }
586
587 /// Get featured plugins
588 ///
589 /// Returns a curated list of high-quality plugins based on ratings,
590 /// download counts, and community engagement metrics.
591 ///
592 /// # Returns
593 ///
594 /// A vector of featured plugins sorted by feature score.
595 ///
596 /// # Examples
597 ///
598 /// ```rust,ignore
599 /// use sklears_core::plugin::PluginMarketplace;
600 ///
601 /// async fn show_featured() -> Result<(), Box<dyn std::error::Error>> {
602 /// let marketplace = PluginMarketplace::new();
603 /// let featured = marketplace.get_featured_plugins().await?;
604 ///
605 /// for plugin in featured {
606 /// println!("{}: {:.1} stars, {} downloads",
607 /// plugin.manifest.metadata.name,
608 /// plugin.rating,
609 /// plugin.download_count);
610 /// }
611 /// Ok(())
612 /// }
613 /// ```
614 pub async fn get_featured_plugins(&self) -> Result<Vec<FeaturedPlugin>> {
615 let plugins = self.discovery.discover_all().await?;
616
617 let mut featured = Vec::new();
618 for plugin_manifest in plugins {
619 let plugin_id = plugin_manifest.metadata.name.clone();
620
621 // Get community metrics
622 let rating = self.rating_system.get_average_rating(&plugin_id).await?;
623 let download_count = self.download_tracker.get_download_count(&plugin_id).await?;
624 let review_count = self.review_system.get_review_count(&plugin_id).await?;
625
626 // Calculate feature score
627 let feature_score = self.calculate_feature_score(rating, download_count, review_count);
628
629 // Only include high-quality plugins
630 if feature_score > 7.0 {
631 featured.push(FeaturedPlugin {
632 manifest: plugin_manifest,
633 rating,
634 download_count,
635 review_count,
636 feature_score,
637 trend_direction: self
638 .analytics
639 .get_trend_direction(&plugin_id)
640 .await
641 .unwrap_or(TrendDirection::Stable),
642 });
643 }
644 }
645
646 // Sort by feature score (descending)
647 featured.sort_by(|a, b| {
648 b.feature_score
649 .partial_cmp(&a.feature_score)
650 .unwrap_or(Ordering::Equal)
651 });
652
653 Ok(featured.into_iter().take(10).collect())
654 }
655
656 /// Submit plugin rating
657 ///
658 /// Allows users to rate plugins on a 1-5 scale. Ratings are used for
659 /// featured plugin selection and recommendation algorithms.
660 ///
661 /// # Arguments
662 ///
663 /// * `plugin_id` - The plugin to rate
664 /// * `user_id` - The user submitting the rating
665 /// * `rating` - Rating value (1.0 to 5.0)
666 ///
667 /// # Errors
668 ///
669 /// Returns an error if the rating is outside the valid range.
670 ///
671 /// # Examples
672 ///
673 /// ```rust,ignore
674 /// use sklears_core::plugin::PluginMarketplace;
675 ///
676 /// async fn rate_plugin() -> Result<(), Box<dyn std::error::Error>> {
677 /// let marketplace = PluginMarketplace::new();
678 /// marketplace.rate_plugin("awesome_classifier", "user123", 4.8).await?;
679 /// println!("Rating submitted successfully");
680 /// Ok(())
681 /// }
682 /// ```
683 pub async fn rate_plugin(&self, plugin_id: &str, user_id: &str, rating: f32) -> Result<()> {
684 if !(1.0..=5.0).contains(&rating) {
685 return Err(SklearsError::InvalidOperation(
686 "Rating must be between 1.0 and 5.0".to_string(),
687 ));
688 }
689
690 self.rating_system
691 .submit_rating(plugin_id, user_id, rating)
692 .await?;
693 self.analytics.track_rating_event(plugin_id, rating).await?;
694
695 Ok(())
696 }
697
698 /// Submit plugin review
699 ///
700 /// Allows users to submit detailed reviews for plugins, providing
701 /// valuable feedback to other users and plugin developers.
702 ///
703 /// # Arguments
704 ///
705 /// * `plugin_id` - The plugin to review
706 /// * `review` - The review content and metadata
707 ///
708 /// # Examples
709 ///
710 /// ```rust,ignore
711 /// use sklears_core::plugin::{PluginMarketplace, PluginReview};
712 ///
713 /// async fn submit_review() -> Result<(), Box<dyn std::error::Error>> {
714 /// let marketplace = PluginMarketplace::new();
715 ///
716 /// let review = PluginReview {
717 /// user_id: "reviewer123".to_string(),
718 /// rating: 4.5,
719 /// title: "Excellent performance".to_string(),
720 /// content: "This plugin significantly improved our model accuracy.".to_string(),
721 /// verified_download: true,
722 /// };
723 ///
724 /// marketplace.submit_review("awesome_classifier", review).await?;
725 /// Ok(())
726 /// }
727 /// ```
728 pub async fn submit_review(&self, plugin_id: &str, review: PluginReview) -> Result<()> {
729 self.review_system.submit_review(plugin_id, review).await?;
730 self.analytics.track_review_event(plugin_id).await?;
731
732 Ok(())
733 }
734
735 /// Get comprehensive plugin statistics
736 ///
737 /// Returns detailed statistics about a plugin including ratings,
738 /// downloads, reviews, and trend data.
739 ///
740 /// # Arguments
741 ///
742 /// * `plugin_id` - The plugin to get statistics for
743 ///
744 /// # Returns
745 ///
746 /// Comprehensive plugin statistics and metrics.
747 ///
748 /// # Examples
749 ///
750 /// ```rust,ignore
751 /// use sklears_core::plugin::PluginMarketplace;
752 ///
753 /// async fn show_stats() -> Result<(), Box<dyn std::error::Error>> {
754 /// let marketplace = PluginMarketplace::new();
755 /// let stats = marketplace.get_plugin_stats("popular_plugin").await?;
756 ///
757 /// println!("Rating: {:.1}/5.0", stats.average_rating);
758 /// println!("Downloads: {}", stats.total_downloads);
759 /// println!("Reviews: {}", stats.recent_reviews.len());
760 /// Ok(())
761 /// }
762 /// ```
763 pub async fn get_plugin_stats(&self, plugin_id: &str) -> Result<PluginStats> {
764 let rating = self.rating_system.get_average_rating(plugin_id).await?;
765 let downloads = self.download_tracker.get_download_count(plugin_id).await?;
766 let reviews = self.review_system.get_reviews(plugin_id, 0, 5).await?;
767 let trend = self.analytics.get_trend_data(plugin_id).await?;
768 let rating_distribution = self
769 .rating_system
770 .get_rating_distribution(plugin_id)
771 .await?;
772
773 Ok(PluginStats {
774 average_rating: rating,
775 total_downloads: downloads,
776 recent_reviews: reviews,
777 trend_data: trend,
778 rating_distribution,
779 monthly_downloads: self
780 .download_tracker
781 .get_monthly_downloads(plugin_id)
782 .await?,
783 last_updated: self.analytics.get_last_update_time(plugin_id).await?,
784 })
785 }
786
787 /// Get trending plugins
788 ///
789 /// Returns plugins that are currently trending based on recent
790 /// download activity, ratings, and community engagement.
791 pub async fn get_trending_plugins(&self, limit: usize) -> Result<Vec<TrendingPlugin>> {
792 let all_plugins = self.discovery.discover_all().await?;
793 let mut trending = Vec::new();
794
795 for manifest in all_plugins {
796 let plugin_id = manifest.metadata.name.clone();
797 let trend_score = self.analytics.calculate_trend_score(&plugin_id).await?;
798
799 if trend_score > 0.5 {
800 // Threshold for trending
801 trending.push(TrendingPlugin {
802 manifest,
803 trend_score,
804 recent_downloads: self
805 .download_tracker
806 .get_recent_downloads(&plugin_id, 7)
807 .await?,
808 velocity: self.analytics.get_download_velocity(&plugin_id).await?,
809 });
810 }
811 }
812
813 trending.sort_by(|a, b| {
814 b.trend_score
815 .partial_cmp(&a.trend_score)
816 .unwrap_or(Ordering::Equal)
817 });
818 Ok(trending.into_iter().take(limit).collect())
819 }
820
821 /// Calculate feature score for plugin ranking
822 ///
823 /// Computes a composite score based on rating, downloads, and reviews
824 /// to determine plugin quality and popularity for featured listings.
825 pub fn calculate_feature_score(&self, rating: f32, downloads: u64, reviews: u64) -> f32 {
826 let rating_weight = 0.4;
827 let download_weight = 0.4;
828 let review_weight = 0.2;
829
830 // Normalize values to 0-10 scale
831 let normalized_rating = rating * 2.0; // 5-star scale to 10-point scale
832
833 // Log scale for downloads (handles zero values)
834 let normalized_downloads = if downloads == 0 {
835 0.0
836 } else {
837 (downloads as f32).log10().min(6.0) / 6.0 * 10.0 // Max at 1M downloads
838 };
839
840 // Log scale for reviews (handles zero values)
841 let normalized_reviews = if reviews == 0 {
842 0.0
843 } else {
844 (reviews as f32).log10().min(3.0) / 3.0 * 10.0 // Max at 1K reviews
845 };
846
847 normalized_rating * rating_weight
848 + normalized_downloads * download_weight
849 + normalized_reviews * review_weight
850 }
851
852 /// Get marketplace analytics summary
853 ///
854 /// Returns overall marketplace statistics and trends.
855 pub async fn get_marketplace_summary(&self) -> Result<MarketplaceSummary> {
856 let total_plugins = self.discovery.discover_all().await?.len();
857 let total_downloads = self.download_tracker.get_total_downloads().await?;
858 let active_users = self.rating_system.get_active_user_count().await?;
859 let trending_categories = self.analytics.get_trending_categories().await?;
860
861 Ok(MarketplaceSummary {
862 total_plugins,
863 total_downloads,
864 active_users,
865 trending_categories,
866 last_updated: std::time::SystemTime::now(),
867 })
868 }
869}
870
871impl Default for PluginMarketplace {
872 fn default() -> Self {
873 Self::new()
874 }
875}
876
877// =============================================================================
878// Supporting Types
879// =============================================================================
880
881/// Plugin repository configuration
882#[derive(Debug, Clone)]
883pub struct PluginRepository {
884 /// Repository name
885 pub name: String,
886 /// Repository URL
887 pub url: String,
888 /// Whether this repository is verified/trusted
889 pub verified: bool,
890 /// Repository priority (higher = checked first)
891 pub priority: u8,
892}
893
894impl PluginRepository {
895 /// Create the official SKLears plugin repository
896 pub fn official() -> Self {
897 Self {
898 name: "Official".to_string(),
899 url: "https://plugins.sklears.rs".to_string(),
900 verified: true,
901 priority: 10,
902 }
903 }
904
905 /// Create the community plugin repository
906 pub fn community() -> Self {
907 Self {
908 name: "Community".to_string(),
909 url: "https://community.sklears.rs".to_string(),
910 verified: true,
911 priority: 5,
912 }
913 }
914}
915
916/// Search query for plugin discovery
917#[derive(Debug, Clone)]
918pub struct SearchQuery {
919 /// Text to search for in plugin names and descriptions
920 pub text: String,
921 /// Filter by plugin category
922 pub category: Option<PluginCategory>,
923 /// Required capabilities
924 pub capabilities: Vec<PluginCapability>,
925 /// Maximum number of results
926 pub limit: Option<usize>,
927 /// Minimum rating filter
928 pub min_rating: Option<f32>,
929}
930
931/// Plugin search result
932#[derive(Debug, Clone)]
933pub struct PluginSearchResult {
934 /// Plugin identifier
935 pub plugin_id: String,
936 /// Plugin description
937 pub description: String,
938 /// Search relevance score
939 pub relevance_score: f32,
940 /// Popularity score
941 pub popularity_score: f32,
942 /// Plugin category
943 pub category: PluginCategory,
944 /// Plugin capabilities
945 pub capabilities: Vec<PluginCapability>,
946 /// Average rating
947 pub rating: f32,
948 /// Download count
949 pub download_count: u64,
950}
951
952/// Plugin installation result
953#[derive(Debug, Clone)]
954pub struct PluginInstallResult {
955 /// Plugin manifest
956 pub manifest: PluginManifest,
957 /// Installation path
958 pub install_path: String,
959 /// Validation report
960 pub validation_report: ValidationReport,
961}
962
963/// Featured plugin information
964#[derive(Debug, Clone)]
965pub struct FeaturedPlugin {
966 /// Plugin manifest
967 pub manifest: PluginManifest,
968 /// Average rating
969 pub rating: f32,
970 /// Total download count
971 pub download_count: u64,
972 /// Number of reviews
973 pub review_count: u64,
974 /// Feature score (0-10)
975 pub feature_score: f32,
976 /// Trend direction
977 pub trend_direction: TrendDirection,
978}
979
980/// Plugin review
981#[derive(Debug, Clone)]
982pub struct PluginReview {
983 /// User who submitted the review
984 pub user_id: String,
985 /// Rating given (1-5)
986 pub rating: f32,
987 /// Review title
988 pub title: String,
989 /// Review content
990 pub content: String,
991 /// Whether user has verified download
992 pub verified_download: bool,
993}
994
995/// Comprehensive plugin statistics
996#[derive(Debug, Clone)]
997pub struct PluginStats {
998 /// Average rating
999 pub average_rating: f32,
1000 /// Total downloads
1001 pub total_downloads: u64,
1002 /// Recent reviews
1003 pub recent_reviews: Vec<PluginReview>,
1004 /// Trend data points
1005 pub trend_data: Vec<f32>,
1006 /// Rating distribution (1-5 stars)
1007 pub rating_distribution: HashMap<u8, u64>,
1008 /// Monthly download counts
1009 pub monthly_downloads: Vec<u64>,
1010 /// Last update time
1011 pub last_updated: std::time::SystemTime,
1012}
1013
1014/// Trending plugin information
1015#[derive(Debug, Clone)]
1016pub struct TrendingPlugin {
1017 /// Plugin manifest
1018 pub manifest: PluginManifest,
1019 /// Trend score
1020 pub trend_score: f32,
1021 /// Recent downloads
1022 pub recent_downloads: u64,
1023 /// Download velocity (downloads per day)
1024 pub velocity: f32,
1025}
1026
1027/// Repository statistics
1028#[derive(Debug, Clone)]
1029pub struct RepositoryStats {
1030 /// Repository name
1031 pub name: String,
1032 /// Repository URL
1033 pub url: String,
1034 /// Number of plugins
1035 pub plugin_count: usize,
1036 /// Whether repository is verified
1037 pub verified: bool,
1038 /// Last update time
1039 pub last_updated: std::time::SystemTime,
1040}
1041
1042/// Marketplace summary statistics
1043#[derive(Debug, Clone)]
1044pub struct MarketplaceSummary {
1045 /// Total number of plugins
1046 pub total_plugins: usize,
1047 /// Total download count across all plugins
1048 pub total_downloads: u64,
1049 /// Number of active users
1050 pub active_users: u64,
1051 /// Trending categories
1052 pub trending_categories: Vec<PluginCategory>,
1053 /// Last update time
1054 pub last_updated: std::time::SystemTime,
1055}
1056
1057/// Trend direction enumeration
1058#[derive(Debug, Clone, PartialEq)]
1059pub enum TrendDirection {
1060 Rising,
1061 Stable,
1062 Declining,
1063}
1064
1065/// Marketplace specific metadata
1066#[derive(Debug, Clone)]
1067pub struct MarketplaceInfo {
1068 pub tags: Vec<String>,
1069 pub license: String,
1070 pub repository_url: Option<String>,
1071 pub documentation_url: Option<String>,
1072 pub pricing: PricingInfo,
1073 pub support_level: SupportLevel,
1074}
1075
1076/// Plugin pricing information
1077#[derive(Debug, Clone)]
1078pub enum PricingInfo {
1079 Free,
1080 OneTime(f64),
1081 Subscription(f64, SubscriptionPeriod),
1082 UsageBased(f64, UsageUnit),
1083}
1084
1085/// Subscription period
1086#[derive(Debug, Clone)]
1087pub enum SubscriptionPeriod {
1088 Monthly,
1089 Yearly,
1090}
1091
1092/// Usage unit for pricing
1093#[derive(Debug, Clone)]
1094pub enum UsageUnit {
1095 PerPrediction,
1096 PerDataPoint,
1097 PerHour,
1098}
1099
1100/// Support level offered
1101#[derive(Debug, Clone)]
1102pub enum SupportLevel {
1103 Community,
1104 Basic,
1105 Professional,
1106 Enterprise,
1107}
1108
1109// =============================================================================
1110// Placeholder Implementations for Supporting Services
1111// =============================================================================
1112
1113/// Plugin cache for local storage
1114#[derive(Debug, Clone)]
1115pub struct PluginCache;
1116
1117impl Default for PluginCache {
1118 fn default() -> Self {
1119 Self::new()
1120 }
1121}
1122
1123impl PluginCache {
1124 pub fn new() -> Self {
1125 Self
1126 }
1127
1128 pub fn get_repository_plugins(&self, _url: &str) -> Option<CachedPlugins> {
1129 None
1130 }
1131
1132 pub fn store_repository_plugins(&self, _url: &str, _plugins: &[PluginManifest]) {}
1133}
1134
1135/// Search index for fast plugin lookups
1136#[derive(Debug, Clone)]
1137pub struct SearchIndex;
1138
1139impl Default for SearchIndex {
1140 fn default() -> Self {
1141 Self::new()
1142 }
1143}
1144
1145impl SearchIndex {
1146 pub fn new() -> Self {
1147 Self
1148 }
1149
1150 pub fn index_plugins(&self, _plugins: &[PluginManifest]) {}
1151
1152 pub fn search(&self, _query: &SearchQuery) -> Result<Vec<PluginSearchResult>> {
1153 Ok(Vec::new())
1154 }
1155}
1156
1157/// Network client for remote operations
1158#[derive(Debug, Clone)]
1159pub struct NetworkClient;
1160
1161impl Default for NetworkClient {
1162 fn default() -> Self {
1163 Self::new()
1164 }
1165}
1166
1167impl NetworkClient {
1168 pub fn new() -> Self {
1169 Self
1170 }
1171
1172 pub async fn fetch_repository_plugins(
1173 &self,
1174 _repo: &PluginRepository,
1175 ) -> Result<Vec<PluginManifest>> {
1176 Ok(Vec::new())
1177 }
1178
1179 pub async fn search_repository(
1180 &self,
1181 _repo: &PluginRepository,
1182 _query: &SearchQuery,
1183 ) -> Result<Vec<PluginSearchResult>> {
1184 Ok(Vec::new())
1185 }
1186
1187 pub async fn download_plugin(&self, _manifest: &PluginManifest) -> Result<Vec<u8>> {
1188 Ok(Vec::new())
1189 }
1190
1191 pub async fn get_plugin_manifest(
1192 &self,
1193 _repo: &PluginRepository,
1194 _id: &str,
1195 _version: Option<&str>,
1196 ) -> Result<PluginManifest> {
1197 Err(SklearsError::InvalidOperation("Not found".to_string()))
1198 }
1199}
1200
1201/// Rating system for community feedback
1202#[derive(Debug, Clone)]
1203pub struct RatingSystem;
1204
1205impl Default for RatingSystem {
1206 fn default() -> Self {
1207 Self::new()
1208 }
1209}
1210
1211impl RatingSystem {
1212 pub fn new() -> Self {
1213 Self
1214 }
1215
1216 pub async fn get_average_rating(&self, _plugin_id: &str) -> Result<f32> {
1217 Ok(4.5)
1218 }
1219
1220 pub async fn submit_rating(
1221 &self,
1222 _plugin_id: &str,
1223 _user_id: &str,
1224 _rating: f32,
1225 ) -> Result<()> {
1226 Ok(())
1227 }
1228
1229 pub async fn get_rating_distribution(&self, _plugin_id: &str) -> Result<HashMap<u8, u64>> {
1230 Ok(HashMap::new())
1231 }
1232
1233 pub async fn get_active_user_count(&self) -> Result<u64> {
1234 Ok(1000)
1235 }
1236}
1237
1238/// Review system for detailed feedback
1239#[derive(Debug, Clone)]
1240pub struct ReviewSystem;
1241
1242impl Default for ReviewSystem {
1243 fn default() -> Self {
1244 Self::new()
1245 }
1246}
1247
1248impl ReviewSystem {
1249 pub fn new() -> Self {
1250 Self
1251 }
1252
1253 pub async fn get_review_count(&self, _plugin_id: &str) -> Result<u64> {
1254 Ok(100)
1255 }
1256
1257 pub async fn submit_review(&self, _plugin_id: &str, _review: PluginReview) -> Result<()> {
1258 Ok(())
1259 }
1260
1261 pub async fn get_reviews(
1262 &self,
1263 _plugin_id: &str,
1264 _offset: usize,
1265 _limit: usize,
1266 ) -> Result<Vec<PluginReview>> {
1267 Ok(Vec::new())
1268 }
1269}
1270
1271/// Download tracking for popularity metrics
1272#[derive(Debug, Clone)]
1273pub struct DownloadTracker;
1274
1275impl Default for DownloadTracker {
1276 fn default() -> Self {
1277 Self::new()
1278 }
1279}
1280
1281impl DownloadTracker {
1282 pub fn new() -> Self {
1283 Self
1284 }
1285
1286 pub async fn get_download_count(&self, _plugin_id: &str) -> Result<u64> {
1287 Ok(1000)
1288 }
1289
1290 pub async fn get_monthly_downloads(&self, _plugin_id: &str) -> Result<Vec<u64>> {
1291 Ok(vec![100, 120, 150, 180])
1292 }
1293
1294 pub async fn get_recent_downloads(&self, _plugin_id: &str, _days: u32) -> Result<u64> {
1295 Ok(50)
1296 }
1297
1298 pub async fn get_total_downloads(&self) -> Result<u64> {
1299 Ok(1000000)
1300 }
1301}
1302
1303/// Analytics engine for trend analysis
1304#[derive(Debug, Clone)]
1305pub struct PluginAnalytics;
1306
1307impl Default for PluginAnalytics {
1308 fn default() -> Self {
1309 Self::new()
1310 }
1311}
1312
1313impl PluginAnalytics {
1314 pub fn new() -> Self {
1315 Self
1316 }
1317
1318 pub async fn track_rating_event(&self, _plugin_id: &str, _rating: f32) -> Result<()> {
1319 Ok(())
1320 }
1321
1322 pub async fn track_review_event(&self, _plugin_id: &str) -> Result<()> {
1323 Ok(())
1324 }
1325
1326 pub async fn get_trend_data(&self, _plugin_id: &str) -> Result<Vec<f32>> {
1327 Ok(vec![1.0, 1.2, 1.5, 1.8])
1328 }
1329
1330 pub async fn get_trend_direction(&self, _plugin_id: &str) -> Result<TrendDirection> {
1331 Ok(TrendDirection::Stable)
1332 }
1333
1334 pub async fn calculate_trend_score(&self, _plugin_id: &str) -> Result<f32> {
1335 Ok(0.7)
1336 }
1337
1338 pub async fn get_download_velocity(&self, _plugin_id: &str) -> Result<f32> {
1339 Ok(5.0)
1340 }
1341
1342 pub async fn get_last_update_time(&self, _plugin_id: &str) -> Result<std::time::SystemTime> {
1343 Ok(std::time::SystemTime::now())
1344 }
1345
1346 pub async fn get_trending_categories(&self) -> Result<Vec<PluginCategory>> {
1347 Ok(vec![PluginCategory::Algorithm, PluginCategory::Transformer])
1348 }
1349}
1350
1351/// Cached plugins with expiration
1352#[derive(Debug, Clone)]
1353pub struct CachedPlugins {
1354 pub plugins: Vec<PluginManifest>,
1355}
1356
1357impl CachedPlugins {
1358 pub fn is_expired(&self) -> bool {
1359 false // Placeholder implementation
1360 }
1361}
1362
1363/// Dummy plugin for validation testing
1364#[derive(Debug)]
1365pub struct DummyPlugin;
1366
1367impl DummyPlugin {
1368 #[allow(clippy::new_ret_no_self)]
1369 pub fn new() -> Box<dyn Plugin> {
1370 Box::new(Self)
1371 }
1372}
1373
1374impl Plugin for DummyPlugin {
1375 fn id(&self) -> &str {
1376 "dummy"
1377 }
1378
1379 fn metadata(&self) -> PluginMetadata {
1380 PluginMetadata::default()
1381 }
1382
1383 fn initialize(&mut self, _config: &super::types_config::PluginConfig) -> Result<()> {
1384 Ok(())
1385 }
1386
1387 fn is_compatible(&self, _input_type: std::any::TypeId) -> bool {
1388 true
1389 }
1390
1391 fn as_any(&self) -> &dyn std::any::Any {
1392 self
1393 }
1394
1395 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
1396 self
1397 }
1398
1399 fn validate_config(&self, _config: &super::types_config::PluginConfig) -> Result<()> {
1400 Ok(())
1401 }
1402
1403 fn cleanup(&mut self) -> Result<()> {
1404 Ok(())
1405 }
1406}