use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use ggen_utils::error::Result;
#[async_trait]
pub trait MarketplaceRegistry: Send + Sync {
async fn get_package(&self, id: &str) -> Result<PackageInfo>;
async fn list_all(&self) -> Result<Vec<PackageInfo>>;
async fn publish(&self, package: &PackagePublish) -> Result<PublishSuccess>;
async fn search(&self, query: &str) -> Result<Vec<SearchMatch>>;
async fn search_by_keyword(&self, keyword: &str) -> Result<Vec<SearchMatch>>;
async fn search_by_author(&self, author: &str) -> Result<Vec<SearchMatch>>;
async fn search_by_quality(&self, min_score: u32) -> Result<Vec<SearchMatch>>;
async fn search_by_description(&self, text: &str) -> Result<Vec<SearchMatch>>;
async fn list_versions(&self, package_id: &str) -> Result<Vec<VersionInfo>>;
async fn trending_packages(&self, limit: usize) -> Result<Vec<SearchMatch>>;
async fn recent_packages(&self, limit: usize) -> Result<Vec<SearchMatch>>;
async fn validate_package(&self, package: &PackageInfo) -> Result<ValidationResult>;
async fn validate_all(&self) -> Result<Vec<ValidationResult>>;
async fn get_recommendations(&self, package_id: &str) -> Result<Vec<Recommendation>>;
async fn compare_packages(&self, ids: &[String]) -> Result<ComparisonResult>;
async fn resolve_dependencies(&self, package_id: &str) -> Result<Vec<DependencyInfo>>;
async fn get_installation_manifest(&self, package_id: &str) -> Result<InstallationManifest>;
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PackageInfo {
pub id: String,
pub name: String,
pub description: String,
pub version: String,
pub author: String,
pub quality_score: u32,
pub downloads: u64,
pub is_production_ready: bool,
pub repository: Option<String>,
pub license: Option<String>,
pub homepage: Option<String>,
pub location: PathBuf,
pub size_bytes: u64,
pub published_at: DateTime<Utc>,
pub last_used: DateTime<Utc>,
pub created_at: DateTime<Utc>,
pub is_deprecated: bool,
pub deprecation_notice: Option<String>,
#[serde(default)]
pub metadata: HashMap<String, String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PackagePublish {
pub name: String,
pub description: String,
pub version: String,
pub author: String,
pub dependencies: Vec<String>,
pub repository: Option<String>,
pub license: Option<String>,
pub readme: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct SearchMatch {
pub package_id: String,
pub name: String,
pub version: String,
pub relevance_score: f64, pub description: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct VersionInfo {
pub version: String,
pub published_at: String,
pub is_stable: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ValidationResult {
pub package_id: String,
pub is_valid: bool,
pub quality_score: u32,
pub checks_passed: Vec<String>,
pub checks_failed: Vec<String>,
pub warnings: Vec<String>,
pub recommendations: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Recommendation {
pub package_id: String,
pub reason: RecommendationReason,
pub similarity_score: f64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum RecommendationReason {
SimilarDependencies,
SameAuthor,
SimilarQuality,
ComplementaryFunctionality,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ComparisonResult {
pub packages: Vec<PackageComparison>,
pub summary: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PackageComparison {
pub package_id: String,
pub properties: HashMap<String, String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DependencyInfo {
pub package_id: String,
pub required_version: String,
pub is_optional: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct InstallationManifest {
pub package_id: String,
pub dependencies: Vec<DependencyInfo>,
pub install_order: Vec<String>,
pub estimated_download_size: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PublishSuccess {
pub package_id: String,
pub version: String,
pub url: String,
pub message: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(clippy::expect_used)]
#[test]
fn test_trait_is_object_safe() {
fn _takes_dyn_registry(_: &dyn MarketplaceRegistry) {}
}
#[allow(clippy::expect_used)]
#[test]
fn test_package_info_serialization() {
let pkg = PackageInfo {
id: "test-pkg".to_string(),
name: "Test Package".to_string(),
description: "A test package".to_string(),
version: "1.0.0".to_string(),
author: "Test Author".to_string(),
quality_score: 85,
downloads: 1000,
is_production_ready: true,
repository: Some("https://example.com".to_string()),
license: Some("MIT".to_string()),
homepage: Some("https://example.com/docs".to_string()),
location: PathBuf::from("/opt/packs/test"),
size_bytes: 1_024_000,
published_at: Utc::now(),
last_used: Utc::now(),
created_at: Utc::now(),
is_deprecated: false,
deprecation_notice: None,
metadata: Default::default(),
};
let json = serde_json::to_string(&pkg).expect("serialization failed");
let deserialized: PackageInfo =
serde_json::from_str(&json).expect("deserialization failed");
assert_eq!(pkg, deserialized);
}
#[test]
fn test_search_match_relevance_bounds() {
let mut matches = vec![
SearchMatch {
package_id: "pkg1".to_string(),
name: "Package 1".to_string(),
version: "1.0.0".to_string(),
relevance_score: 1.0,
description: None,
},
SearchMatch {
package_id: "pkg2".to_string(),
name: "Package 2".to_string(),
version: "2.0.0".to_string(),
relevance_score: 0.5,
description: None,
},
];
matches.sort_by(|a, b| b.relevance_score.partial_cmp(&a.relevance_score).unwrap());
assert_eq!(matches[0].package_id, "pkg1");
assert_eq!(matches[1].package_id, "pkg2");
}
#[test]
fn test_validation_result_completeness() {
let result = ValidationResult {
package_id: "test".to_string(),
is_valid: true,
quality_score: 95,
checks_passed: vec!["readme".to_string(), "tests".to_string()],
checks_failed: vec![],
warnings: vec!["outdated dependency".to_string()],
recommendations: vec!["add changelog".to_string()],
};
assert!(result.is_valid);
assert_eq!(result.checks_passed.len(), 2);
assert_eq!(result.warnings.len(), 1);
assert_eq!(result.recommendations.len(), 1);
}
}