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(|a, b| b.priority.cmp(&a.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 = format!("/tmp/plugins/{}", manifest.metadata.name);
436 std::fs::create_dir_all(&plugin_dir).map_err(|e| {
437 SklearsError::InvalidOperation(format!("Failed to create plugin directory: {}", e))
438 })?;
439
440 // Write plugin data
441 let plugin_file = format!("{}/plugin.so", plugin_dir);
442 std::fs::write(&plugin_file, data).map_err(|e| {
443 SklearsError::InvalidOperation(format!("Failed to write plugin file: {}", e))
444 })?;
445
446 // Set appropriate permissions (readable and executable)
447 #[cfg(unix)]
448 {
449 use std::os::unix::fs::PermissionsExt;
450 std::fs::set_permissions(&plugin_file, std::fs::Permissions::from_mode(0o755))
451 .map_err(|e| {
452 SklearsError::InvalidOperation(format!("Failed to set permissions: {}", e))
453 })?;
454 }
455
456 // Write manifest for future reference
457 let manifest_file = format!("{}/manifest.json", plugin_dir);
458 let manifest_json = serde_json::to_string_pretty(manifest).map_err(|e| {
459 SklearsError::InvalidOperation(format!("Failed to serialize manifest: {}", e))
460 })?;
461 std::fs::write(manifest_file, manifest_json).map_err(|e| {
462 SklearsError::InvalidOperation(format!("Failed to write manifest: {}", e))
463 })?;
464
465 Ok(plugin_file)
466 }
467
468 /// Compute content hash for verification
469 fn compute_content_hash(&self, data: &[u8]) -> String {
470 // Simple hash implementation - in production, use SHA-256 or similar
471 use std::collections::hash_map::DefaultHasher;
472 use std::hash::{Hash, Hasher};
473
474 let mut hasher = DefaultHasher::new();
475 data.hash(&mut hasher);
476 format!("{:x}", hasher.finish())
477 }
478
479 /// Get repository statistics
480 ///
481 /// Returns statistics about configured repositories and their status.
482 pub async fn get_repository_stats(&self) -> Vec<RepositoryStats> {
483 let mut stats = Vec::new();
484
485 for repository in &self.repositories {
486 let plugin_count = match self.discover_from_repository(repository).await {
487 Ok(plugins) => plugins.len(),
488 Err(_) => 0,
489 };
490
491 stats.push(RepositoryStats {
492 name: repository.name.clone(),
493 url: repository.url.clone(),
494 plugin_count,
495 verified: repository.verified,
496 last_updated: std::time::SystemTime::now(), // Placeholder
497 });
498 }
499
500 stats
501 }
502}
503
504impl Default for PluginDiscoveryService {
505 fn default() -> Self {
506 Self::new()
507 }
508}
509
510/// Community plugin marketplace
511///
512/// The PluginMarketplace provides a comprehensive platform for plugin discovery,
513/// rating, reviewing, and analytics. It combines the discovery service with
514/// community features to create a full marketplace experience.
515///
516/// # Features
517///
518/// - Featured plugin recommendations
519/// - Community ratings and reviews
520/// - Download tracking and analytics
521/// - Plugin popularity scoring
522/// - Trend analysis and reporting
523///
524/// # Examples
525///
526/// ```rust,ignore
527/// use sklears_core::plugin::PluginMarketplace;
528///
529/// async fn marketplace_demo() -> Result<(), Box<dyn std::error::Error>> {
530/// let marketplace = PluginMarketplace::new();
531///
532/// // Get featured plugins
533/// let featured = marketplace.get_featured_plugins().await?;
534/// println!("Featured plugins: {}", featured.len());
535///
536/// // Rate a plugin
537/// marketplace.rate_plugin("linear_regression", "user123", 4.5).await?;
538///
539/// // Get plugin statistics
540/// let stats = marketplace.get_plugin_stats("linear_regression").await?;
541/// println!("Average rating: {:.1}", stats.average_rating);
542///
543/// Ok(())
544/// }
545/// ```
546#[derive(Debug)]
547pub struct PluginMarketplace {
548 /// Discovery service for plugin management
549 discovery: PluginDiscoveryService,
550 /// Rating system for community feedback
551 rating_system: RatingSystem,
552 /// Review system for detailed feedback
553 review_system: ReviewSystem,
554 /// Download tracking for popularity metrics
555 download_tracker: DownloadTracker,
556 /// Analytics engine for trend analysis
557 analytics: PluginAnalytics,
558}
559
560impl PluginMarketplace {
561 /// Create a new marketplace
562 ///
563 /// Initializes all marketplace components including discovery, ratings,
564 /// reviews, and analytics systems.
565 ///
566 /// # Examples
567 ///
568 /// ```rust
569 /// use sklears_core::plugin::PluginMarketplace;
570 ///
571 /// let marketplace = PluginMarketplace::new();
572 /// ```
573 pub fn new() -> Self {
574 Self {
575 discovery: PluginDiscoveryService::new(),
576 rating_system: RatingSystem::new(),
577 review_system: ReviewSystem::new(),
578 download_tracker: DownloadTracker::new(),
579 analytics: PluginAnalytics::new(),
580 }
581 }
582
583 /// Get featured plugins
584 ///
585 /// Returns a curated list of high-quality plugins based on ratings,
586 /// download counts, and community engagement metrics.
587 ///
588 /// # Returns
589 ///
590 /// A vector of featured plugins sorted by feature score.
591 ///
592 /// # Examples
593 ///
594 /// ```rust,ignore
595 /// use sklears_core::plugin::PluginMarketplace;
596 ///
597 /// async fn show_featured() -> Result<(), Box<dyn std::error::Error>> {
598 /// let marketplace = PluginMarketplace::new();
599 /// let featured = marketplace.get_featured_plugins().await?;
600 ///
601 /// for plugin in featured {
602 /// println!("{}: {:.1} stars, {} downloads",
603 /// plugin.manifest.metadata.name,
604 /// plugin.rating,
605 /// plugin.download_count);
606 /// }
607 /// Ok(())
608 /// }
609 /// ```
610 pub async fn get_featured_plugins(&self) -> Result<Vec<FeaturedPlugin>> {
611 let plugins = self.discovery.discover_all().await?;
612
613 let mut featured = Vec::new();
614 for plugin_manifest in plugins {
615 let plugin_id = plugin_manifest.metadata.name.clone();
616
617 // Get community metrics
618 let rating = self.rating_system.get_average_rating(&plugin_id).await?;
619 let download_count = self.download_tracker.get_download_count(&plugin_id).await?;
620 let review_count = self.review_system.get_review_count(&plugin_id).await?;
621
622 // Calculate feature score
623 let feature_score = self.calculate_feature_score(rating, download_count, review_count);
624
625 // Only include high-quality plugins
626 if feature_score > 7.0 {
627 featured.push(FeaturedPlugin {
628 manifest: plugin_manifest,
629 rating,
630 download_count,
631 review_count,
632 feature_score,
633 trend_direction: self
634 .analytics
635 .get_trend_direction(&plugin_id)
636 .await
637 .unwrap_or(TrendDirection::Stable),
638 });
639 }
640 }
641
642 // Sort by feature score (descending)
643 featured.sort_by(|a, b| {
644 b.feature_score
645 .partial_cmp(&a.feature_score)
646 .unwrap_or(Ordering::Equal)
647 });
648
649 Ok(featured.into_iter().take(10).collect())
650 }
651
652 /// Submit plugin rating
653 ///
654 /// Allows users to rate plugins on a 1-5 scale. Ratings are used for
655 /// featured plugin selection and recommendation algorithms.
656 ///
657 /// # Arguments
658 ///
659 /// * `plugin_id` - The plugin to rate
660 /// * `user_id` - The user submitting the rating
661 /// * `rating` - Rating value (1.0 to 5.0)
662 ///
663 /// # Errors
664 ///
665 /// Returns an error if the rating is outside the valid range.
666 ///
667 /// # Examples
668 ///
669 /// ```rust,ignore
670 /// use sklears_core::plugin::PluginMarketplace;
671 ///
672 /// async fn rate_plugin() -> Result<(), Box<dyn std::error::Error>> {
673 /// let marketplace = PluginMarketplace::new();
674 /// marketplace.rate_plugin("awesome_classifier", "user123", 4.8).await?;
675 /// println!("Rating submitted successfully");
676 /// Ok(())
677 /// }
678 /// ```
679 pub async fn rate_plugin(&self, plugin_id: &str, user_id: &str, rating: f32) -> Result<()> {
680 if !(1.0..=5.0).contains(&rating) {
681 return Err(SklearsError::InvalidOperation(
682 "Rating must be between 1.0 and 5.0".to_string(),
683 ));
684 }
685
686 self.rating_system
687 .submit_rating(plugin_id, user_id, rating)
688 .await?;
689 self.analytics.track_rating_event(plugin_id, rating).await?;
690
691 Ok(())
692 }
693
694 /// Submit plugin review
695 ///
696 /// Allows users to submit detailed reviews for plugins, providing
697 /// valuable feedback to other users and plugin developers.
698 ///
699 /// # Arguments
700 ///
701 /// * `plugin_id` - The plugin to review
702 /// * `review` - The review content and metadata
703 ///
704 /// # Examples
705 ///
706 /// ```rust,ignore
707 /// use sklears_core::plugin::{PluginMarketplace, PluginReview};
708 ///
709 /// async fn submit_review() -> Result<(), Box<dyn std::error::Error>> {
710 /// let marketplace = PluginMarketplace::new();
711 ///
712 /// let review = PluginReview {
713 /// user_id: "reviewer123".to_string(),
714 /// rating: 4.5,
715 /// title: "Excellent performance".to_string(),
716 /// content: "This plugin significantly improved our model accuracy.".to_string(),
717 /// verified_download: true,
718 /// };
719 ///
720 /// marketplace.submit_review("awesome_classifier", review).await?;
721 /// Ok(())
722 /// }
723 /// ```
724 pub async fn submit_review(&self, plugin_id: &str, review: PluginReview) -> Result<()> {
725 self.review_system.submit_review(plugin_id, review).await?;
726 self.analytics.track_review_event(plugin_id).await?;
727
728 Ok(())
729 }
730
731 /// Get comprehensive plugin statistics
732 ///
733 /// Returns detailed statistics about a plugin including ratings,
734 /// downloads, reviews, and trend data.
735 ///
736 /// # Arguments
737 ///
738 /// * `plugin_id` - The plugin to get statistics for
739 ///
740 /// # Returns
741 ///
742 /// Comprehensive plugin statistics and metrics.
743 ///
744 /// # Examples
745 ///
746 /// ```rust,ignore
747 /// use sklears_core::plugin::PluginMarketplace;
748 ///
749 /// async fn show_stats() -> Result<(), Box<dyn std::error::Error>> {
750 /// let marketplace = PluginMarketplace::new();
751 /// let stats = marketplace.get_plugin_stats("popular_plugin").await?;
752 ///
753 /// println!("Rating: {:.1}/5.0", stats.average_rating);
754 /// println!("Downloads: {}", stats.total_downloads);
755 /// println!("Reviews: {}", stats.recent_reviews.len());
756 /// Ok(())
757 /// }
758 /// ```
759 pub async fn get_plugin_stats(&self, plugin_id: &str) -> Result<PluginStats> {
760 let rating = self.rating_system.get_average_rating(plugin_id).await?;
761 let downloads = self.download_tracker.get_download_count(plugin_id).await?;
762 let reviews = self.review_system.get_reviews(plugin_id, 0, 5).await?;
763 let trend = self.analytics.get_trend_data(plugin_id).await?;
764 let rating_distribution = self
765 .rating_system
766 .get_rating_distribution(plugin_id)
767 .await?;
768
769 Ok(PluginStats {
770 average_rating: rating,
771 total_downloads: downloads,
772 recent_reviews: reviews,
773 trend_data: trend,
774 rating_distribution,
775 monthly_downloads: self
776 .download_tracker
777 .get_monthly_downloads(plugin_id)
778 .await?,
779 last_updated: self.analytics.get_last_update_time(plugin_id).await?,
780 })
781 }
782
783 /// Get trending plugins
784 ///
785 /// Returns plugins that are currently trending based on recent
786 /// download activity, ratings, and community engagement.
787 pub async fn get_trending_plugins(&self, limit: usize) -> Result<Vec<TrendingPlugin>> {
788 let all_plugins = self.discovery.discover_all().await?;
789 let mut trending = Vec::new();
790
791 for manifest in all_plugins {
792 let plugin_id = manifest.metadata.name.clone();
793 let trend_score = self.analytics.calculate_trend_score(&plugin_id).await?;
794
795 if trend_score > 0.5 {
796 // Threshold for trending
797 trending.push(TrendingPlugin {
798 manifest,
799 trend_score,
800 recent_downloads: self
801 .download_tracker
802 .get_recent_downloads(&plugin_id, 7)
803 .await?,
804 velocity: self.analytics.get_download_velocity(&plugin_id).await?,
805 });
806 }
807 }
808
809 trending.sort_by(|a, b| {
810 b.trend_score
811 .partial_cmp(&a.trend_score)
812 .unwrap_or(Ordering::Equal)
813 });
814 Ok(trending.into_iter().take(limit).collect())
815 }
816
817 /// Calculate feature score for plugin ranking
818 ///
819 /// Computes a composite score based on rating, downloads, and reviews
820 /// to determine plugin quality and popularity for featured listings.
821 pub fn calculate_feature_score(&self, rating: f32, downloads: u64, reviews: u64) -> f32 {
822 let rating_weight = 0.4;
823 let download_weight = 0.4;
824 let review_weight = 0.2;
825
826 // Normalize values to 0-10 scale
827 let normalized_rating = rating * 2.0; // 5-star scale to 10-point scale
828
829 // Log scale for downloads (handles zero values)
830 let normalized_downloads = if downloads == 0 {
831 0.0
832 } else {
833 (downloads as f32).log10().min(6.0) / 6.0 * 10.0 // Max at 1M downloads
834 };
835
836 // Log scale for reviews (handles zero values)
837 let normalized_reviews = if reviews == 0 {
838 0.0
839 } else {
840 (reviews as f32).log10().min(3.0) / 3.0 * 10.0 // Max at 1K reviews
841 };
842
843 normalized_rating * rating_weight
844 + normalized_downloads * download_weight
845 + normalized_reviews * review_weight
846 }
847
848 /// Get marketplace analytics summary
849 ///
850 /// Returns overall marketplace statistics and trends.
851 pub async fn get_marketplace_summary(&self) -> Result<MarketplaceSummary> {
852 let total_plugins = self.discovery.discover_all().await?.len();
853 let total_downloads = self.download_tracker.get_total_downloads().await?;
854 let active_users = self.rating_system.get_active_user_count().await?;
855 let trending_categories = self.analytics.get_trending_categories().await?;
856
857 Ok(MarketplaceSummary {
858 total_plugins,
859 total_downloads,
860 active_users,
861 trending_categories,
862 last_updated: std::time::SystemTime::now(),
863 })
864 }
865}
866
867impl Default for PluginMarketplace {
868 fn default() -> Self {
869 Self::new()
870 }
871}
872
873// =============================================================================
874// Supporting Types
875// =============================================================================
876
877/// Plugin repository configuration
878#[derive(Debug, Clone)]
879pub struct PluginRepository {
880 /// Repository name
881 pub name: String,
882 /// Repository URL
883 pub url: String,
884 /// Whether this repository is verified/trusted
885 pub verified: bool,
886 /// Repository priority (higher = checked first)
887 pub priority: u8,
888}
889
890impl PluginRepository {
891 /// Create the official SKLears plugin repository
892 pub fn official() -> Self {
893 Self {
894 name: "Official".to_string(),
895 url: "https://plugins.sklears.rs".to_string(),
896 verified: true,
897 priority: 10,
898 }
899 }
900
901 /// Create the community plugin repository
902 pub fn community() -> Self {
903 Self {
904 name: "Community".to_string(),
905 url: "https://community.sklears.rs".to_string(),
906 verified: true,
907 priority: 5,
908 }
909 }
910}
911
912/// Search query for plugin discovery
913#[derive(Debug, Clone)]
914pub struct SearchQuery {
915 /// Text to search for in plugin names and descriptions
916 pub text: String,
917 /// Filter by plugin category
918 pub category: Option<PluginCategory>,
919 /// Required capabilities
920 pub capabilities: Vec<PluginCapability>,
921 /// Maximum number of results
922 pub limit: Option<usize>,
923 /// Minimum rating filter
924 pub min_rating: Option<f32>,
925}
926
927/// Plugin search result
928#[derive(Debug, Clone)]
929pub struct PluginSearchResult {
930 /// Plugin identifier
931 pub plugin_id: String,
932 /// Plugin description
933 pub description: String,
934 /// Search relevance score
935 pub relevance_score: f32,
936 /// Popularity score
937 pub popularity_score: f32,
938 /// Plugin category
939 pub category: PluginCategory,
940 /// Plugin capabilities
941 pub capabilities: Vec<PluginCapability>,
942 /// Average rating
943 pub rating: f32,
944 /// Download count
945 pub download_count: u64,
946}
947
948/// Plugin installation result
949#[derive(Debug, Clone)]
950pub struct PluginInstallResult {
951 /// Plugin manifest
952 pub manifest: PluginManifest,
953 /// Installation path
954 pub install_path: String,
955 /// Validation report
956 pub validation_report: ValidationReport,
957}
958
959/// Featured plugin information
960#[derive(Debug, Clone)]
961pub struct FeaturedPlugin {
962 /// Plugin manifest
963 pub manifest: PluginManifest,
964 /// Average rating
965 pub rating: f32,
966 /// Total download count
967 pub download_count: u64,
968 /// Number of reviews
969 pub review_count: u64,
970 /// Feature score (0-10)
971 pub feature_score: f32,
972 /// Trend direction
973 pub trend_direction: TrendDirection,
974}
975
976/// Plugin review
977#[derive(Debug, Clone)]
978pub struct PluginReview {
979 /// User who submitted the review
980 pub user_id: String,
981 /// Rating given (1-5)
982 pub rating: f32,
983 /// Review title
984 pub title: String,
985 /// Review content
986 pub content: String,
987 /// Whether user has verified download
988 pub verified_download: bool,
989}
990
991/// Comprehensive plugin statistics
992#[derive(Debug, Clone)]
993pub struct PluginStats {
994 /// Average rating
995 pub average_rating: f32,
996 /// Total downloads
997 pub total_downloads: u64,
998 /// Recent reviews
999 pub recent_reviews: Vec<PluginReview>,
1000 /// Trend data points
1001 pub trend_data: Vec<f32>,
1002 /// Rating distribution (1-5 stars)
1003 pub rating_distribution: HashMap<u8, u64>,
1004 /// Monthly download counts
1005 pub monthly_downloads: Vec<u64>,
1006 /// Last update time
1007 pub last_updated: std::time::SystemTime,
1008}
1009
1010/// Trending plugin information
1011#[derive(Debug, Clone)]
1012pub struct TrendingPlugin {
1013 /// Plugin manifest
1014 pub manifest: PluginManifest,
1015 /// Trend score
1016 pub trend_score: f32,
1017 /// Recent downloads
1018 pub recent_downloads: u64,
1019 /// Download velocity (downloads per day)
1020 pub velocity: f32,
1021}
1022
1023/// Repository statistics
1024#[derive(Debug, Clone)]
1025pub struct RepositoryStats {
1026 /// Repository name
1027 pub name: String,
1028 /// Repository URL
1029 pub url: String,
1030 /// Number of plugins
1031 pub plugin_count: usize,
1032 /// Whether repository is verified
1033 pub verified: bool,
1034 /// Last update time
1035 pub last_updated: std::time::SystemTime,
1036}
1037
1038/// Marketplace summary statistics
1039#[derive(Debug, Clone)]
1040pub struct MarketplaceSummary {
1041 /// Total number of plugins
1042 pub total_plugins: usize,
1043 /// Total download count across all plugins
1044 pub total_downloads: u64,
1045 /// Number of active users
1046 pub active_users: u64,
1047 /// Trending categories
1048 pub trending_categories: Vec<PluginCategory>,
1049 /// Last update time
1050 pub last_updated: std::time::SystemTime,
1051}
1052
1053/// Trend direction enumeration
1054#[derive(Debug, Clone, PartialEq)]
1055pub enum TrendDirection {
1056 Rising,
1057 Stable,
1058 Declining,
1059}
1060
1061/// Marketplace specific metadata
1062#[derive(Debug, Clone)]
1063pub struct MarketplaceInfo {
1064 pub tags: Vec<String>,
1065 pub license: String,
1066 pub repository_url: Option<String>,
1067 pub documentation_url: Option<String>,
1068 pub pricing: PricingInfo,
1069 pub support_level: SupportLevel,
1070}
1071
1072/// Plugin pricing information
1073#[derive(Debug, Clone)]
1074pub enum PricingInfo {
1075 Free,
1076 OneTime(f64),
1077 Subscription(f64, SubscriptionPeriod),
1078 UsageBased(f64, UsageUnit),
1079}
1080
1081/// Subscription period
1082#[derive(Debug, Clone)]
1083pub enum SubscriptionPeriod {
1084 Monthly,
1085 Yearly,
1086}
1087
1088/// Usage unit for pricing
1089#[derive(Debug, Clone)]
1090pub enum UsageUnit {
1091 PerPrediction,
1092 PerDataPoint,
1093 PerHour,
1094}
1095
1096/// Support level offered
1097#[derive(Debug, Clone)]
1098pub enum SupportLevel {
1099 Community,
1100 Basic,
1101 Professional,
1102 Enterprise,
1103}
1104
1105// =============================================================================
1106// Placeholder Implementations for Supporting Services
1107// =============================================================================
1108
1109/// Plugin cache for local storage
1110#[derive(Debug, Clone)]
1111pub struct PluginCache;
1112
1113impl Default for PluginCache {
1114 fn default() -> Self {
1115 Self::new()
1116 }
1117}
1118
1119impl PluginCache {
1120 pub fn new() -> Self {
1121 Self
1122 }
1123
1124 pub fn get_repository_plugins(&self, _url: &str) -> Option<CachedPlugins> {
1125 None
1126 }
1127
1128 pub fn store_repository_plugins(&self, _url: &str, _plugins: &[PluginManifest]) {}
1129}
1130
1131/// Search index for fast plugin lookups
1132#[derive(Debug, Clone)]
1133pub struct SearchIndex;
1134
1135impl Default for SearchIndex {
1136 fn default() -> Self {
1137 Self::new()
1138 }
1139}
1140
1141impl SearchIndex {
1142 pub fn new() -> Self {
1143 Self
1144 }
1145
1146 pub fn index_plugins(&self, _plugins: &[PluginManifest]) {}
1147
1148 pub fn search(&self, _query: &SearchQuery) -> Result<Vec<PluginSearchResult>> {
1149 Ok(Vec::new())
1150 }
1151}
1152
1153/// Network client for remote operations
1154#[derive(Debug, Clone)]
1155pub struct NetworkClient;
1156
1157impl Default for NetworkClient {
1158 fn default() -> Self {
1159 Self::new()
1160 }
1161}
1162
1163impl NetworkClient {
1164 pub fn new() -> Self {
1165 Self
1166 }
1167
1168 pub async fn fetch_repository_plugins(
1169 &self,
1170 _repo: &PluginRepository,
1171 ) -> Result<Vec<PluginManifest>> {
1172 Ok(Vec::new())
1173 }
1174
1175 pub async fn search_repository(
1176 &self,
1177 _repo: &PluginRepository,
1178 _query: &SearchQuery,
1179 ) -> Result<Vec<PluginSearchResult>> {
1180 Ok(Vec::new())
1181 }
1182
1183 pub async fn download_plugin(&self, _manifest: &PluginManifest) -> Result<Vec<u8>> {
1184 Ok(Vec::new())
1185 }
1186
1187 pub async fn get_plugin_manifest(
1188 &self,
1189 _repo: &PluginRepository,
1190 _id: &str,
1191 _version: Option<&str>,
1192 ) -> Result<PluginManifest> {
1193 Err(SklearsError::InvalidOperation("Not found".to_string()))
1194 }
1195}
1196
1197/// Rating system for community feedback
1198#[derive(Debug, Clone)]
1199pub struct RatingSystem;
1200
1201impl Default for RatingSystem {
1202 fn default() -> Self {
1203 Self::new()
1204 }
1205}
1206
1207impl RatingSystem {
1208 pub fn new() -> Self {
1209 Self
1210 }
1211
1212 pub async fn get_average_rating(&self, _plugin_id: &str) -> Result<f32> {
1213 Ok(4.5)
1214 }
1215
1216 pub async fn submit_rating(
1217 &self,
1218 _plugin_id: &str,
1219 _user_id: &str,
1220 _rating: f32,
1221 ) -> Result<()> {
1222 Ok(())
1223 }
1224
1225 pub async fn get_rating_distribution(&self, _plugin_id: &str) -> Result<HashMap<u8, u64>> {
1226 Ok(HashMap::new())
1227 }
1228
1229 pub async fn get_active_user_count(&self) -> Result<u64> {
1230 Ok(1000)
1231 }
1232}
1233
1234/// Review system for detailed feedback
1235#[derive(Debug, Clone)]
1236pub struct ReviewSystem;
1237
1238impl Default for ReviewSystem {
1239 fn default() -> Self {
1240 Self::new()
1241 }
1242}
1243
1244impl ReviewSystem {
1245 pub fn new() -> Self {
1246 Self
1247 }
1248
1249 pub async fn get_review_count(&self, _plugin_id: &str) -> Result<u64> {
1250 Ok(100)
1251 }
1252
1253 pub async fn submit_review(&self, _plugin_id: &str, _review: PluginReview) -> Result<()> {
1254 Ok(())
1255 }
1256
1257 pub async fn get_reviews(
1258 &self,
1259 _plugin_id: &str,
1260 _offset: usize,
1261 _limit: usize,
1262 ) -> Result<Vec<PluginReview>> {
1263 Ok(Vec::new())
1264 }
1265}
1266
1267/// Download tracking for popularity metrics
1268#[derive(Debug, Clone)]
1269pub struct DownloadTracker;
1270
1271impl Default for DownloadTracker {
1272 fn default() -> Self {
1273 Self::new()
1274 }
1275}
1276
1277impl DownloadTracker {
1278 pub fn new() -> Self {
1279 Self
1280 }
1281
1282 pub async fn get_download_count(&self, _plugin_id: &str) -> Result<u64> {
1283 Ok(1000)
1284 }
1285
1286 pub async fn get_monthly_downloads(&self, _plugin_id: &str) -> Result<Vec<u64>> {
1287 Ok(vec![100, 120, 150, 180])
1288 }
1289
1290 pub async fn get_recent_downloads(&self, _plugin_id: &str, _days: u32) -> Result<u64> {
1291 Ok(50)
1292 }
1293
1294 pub async fn get_total_downloads(&self) -> Result<u64> {
1295 Ok(1000000)
1296 }
1297}
1298
1299/// Analytics engine for trend analysis
1300#[derive(Debug, Clone)]
1301pub struct PluginAnalytics;
1302
1303impl Default for PluginAnalytics {
1304 fn default() -> Self {
1305 Self::new()
1306 }
1307}
1308
1309impl PluginAnalytics {
1310 pub fn new() -> Self {
1311 Self
1312 }
1313
1314 pub async fn track_rating_event(&self, _plugin_id: &str, _rating: f32) -> Result<()> {
1315 Ok(())
1316 }
1317
1318 pub async fn track_review_event(&self, _plugin_id: &str) -> Result<()> {
1319 Ok(())
1320 }
1321
1322 pub async fn get_trend_data(&self, _plugin_id: &str) -> Result<Vec<f32>> {
1323 Ok(vec![1.0, 1.2, 1.5, 1.8])
1324 }
1325
1326 pub async fn get_trend_direction(&self, _plugin_id: &str) -> Result<TrendDirection> {
1327 Ok(TrendDirection::Stable)
1328 }
1329
1330 pub async fn calculate_trend_score(&self, _plugin_id: &str) -> Result<f32> {
1331 Ok(0.7)
1332 }
1333
1334 pub async fn get_download_velocity(&self, _plugin_id: &str) -> Result<f32> {
1335 Ok(5.0)
1336 }
1337
1338 pub async fn get_last_update_time(&self, _plugin_id: &str) -> Result<std::time::SystemTime> {
1339 Ok(std::time::SystemTime::now())
1340 }
1341
1342 pub async fn get_trending_categories(&self) -> Result<Vec<PluginCategory>> {
1343 Ok(vec![PluginCategory::Algorithm, PluginCategory::Transformer])
1344 }
1345}
1346
1347/// Cached plugins with expiration
1348#[derive(Debug, Clone)]
1349pub struct CachedPlugins {
1350 pub plugins: Vec<PluginManifest>,
1351}
1352
1353impl CachedPlugins {
1354 pub fn is_expired(&self) -> bool {
1355 false // Placeholder implementation
1356 }
1357}
1358
1359/// Dummy plugin for validation testing
1360#[derive(Debug)]
1361pub struct DummyPlugin;
1362
1363impl DummyPlugin {
1364 #[allow(clippy::new_ret_no_self)]
1365 pub fn new() -> Box<dyn Plugin> {
1366 Box::new(Self)
1367 }
1368}
1369
1370impl Plugin for DummyPlugin {
1371 fn id(&self) -> &str {
1372 "dummy"
1373 }
1374
1375 fn metadata(&self) -> PluginMetadata {
1376 PluginMetadata::default()
1377 }
1378
1379 fn initialize(&mut self, _config: &super::types_config::PluginConfig) -> Result<()> {
1380 Ok(())
1381 }
1382
1383 fn is_compatible(&self, _input_type: std::any::TypeId) -> bool {
1384 true
1385 }
1386
1387 fn as_any(&self) -> &dyn std::any::Any {
1388 self
1389 }
1390
1391 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
1392 self
1393 }
1394
1395 fn validate_config(&self, _config: &super::types_config::PluginConfig) -> Result<()> {
1396 Ok(())
1397 }
1398
1399 fn cleanup(&mut self) -> Result<()> {
1400 Ok(())
1401 }
1402}