use async_trait::async_trait;
use crate::domain::version::SemanticVersion;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, thiserror::Error)]
pub enum RegistryError {
#[error("Crate not found: {0}")]
NotFound(String),
#[error("Version not found: {0}")]
VersionNotFound(String),
#[error("API request failed: {0}")]
ApiError(String),
#[error("Network error: {0}")]
NetworkError(String),
#[error("Authentication failed")]
AuthFailed,
#[error("Version already published: {0}@{1}")]
AlreadyPublished(String, SemanticVersion),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Serialization error: {0}")]
SerializationError(String),
}
#[async_trait]
pub trait Registry: Send + Sync {
fn name(&self) -> &str;
async fn get_crate(&self, name: &str) -> Result<Option<CrateInfo>, RegistryError>;
async fn is_published(
&self,
name: &str,
version: &SemanticVersion,
) -> Result<bool, RegistryError>;
async fn publish(&self, crate_data: &CratePackage) -> Result<PublishResult, RegistryError>;
async fn get_download_stats(
&self,
name: &str,
version: &SemanticVersion,
) -> Result<DownloadStats, RegistryError>;
async fn list_owners(&self, name: &str) -> Result<Vec<CrateOwner>, RegistryError>;
async fn add_owner(&self, name: &str, username: &str) -> Result<(), RegistryError>;
async fn remove_owner(&self, name: &str, username: &str) -> Result<(), RegistryError>;
async fn get_metadata(&self, name: &str) -> Result<Option<CrateMetadata>, RegistryError>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrateInfo {
pub name: String,
pub latest_version: SemanticVersion,
pub versions: Vec<SemanticVersion>,
pub downloads: u64,
pub description: Option<String>,
pub repository: Option<String>,
pub homepage: Option<String>,
pub documentation: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub recent_downloads: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct CratePackage {
pub name: String,
pub version: SemanticVersion,
pub crate_file: std::path::PathBuf,
pub manifest_path: std::path::PathBuf,
pub token: String,
pub dry_run: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PublishResult {
pub success: bool,
pub crate_name: String,
pub version: SemanticVersion,
pub duration_ms: u64,
pub crates_io_url: String,
pub warnings: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DownloadStats {
pub crate_name: String,
pub version: SemanticVersion,
pub total: u64,
pub recent_downloads: u64,
pub daily_downloads: Vec<DailyDownloads>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DailyDownloads {
pub date: DateTime<Utc>,
pub downloads: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrateOwner {
pub username: String,
pub name: Option<String>,
pub avatar: Option<String>,
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrateMetadata {
pub name: String,
pub versions: Vec<VersionMetadata>,
pub keywords: Vec<String>,
pub categories: Vec<String>,
pub badges: Vec<Badge>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionMetadata {
pub version: SemanticVersion,
pub checksum: String,
pub dl_path: String,
pub published_at: DateTime<Utc>,
pub features: std::collections::HashMap<String, Vec<String>>,
pub dependencies: Vec<DependencyInfo>,
pub yanked: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DependencyInfo {
pub name: String,
pub req: String,
pub dev: bool,
pub build: bool,
pub optional: bool,
pub features: Vec<String>,
pub target: Option<String>,
pub rename: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Badge {
pub badge_type: String,
pub attributes: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct RegistryConfig {
pub name: String,
pub api_url: String,
pub upload_url: String,
pub token: Option<String>,
pub use_ssh: bool,
}
impl Default for RegistryConfig {
fn default() -> Self {
Self {
name: "crates.io".to_string(),
api_url: "https://crates.io/api/v1".to_string(),
upload_url: "https://crates.io".to_string(),
token: None,
use_ssh: false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registry_config_default() {
let config = RegistryConfig::default();
assert_eq!(config.name, "crates.io");
assert_eq!(config.api_url, "https://crates.io/api/v1");
}
#[test]
fn test_publish_result_serialization() {
let result = PublishResult {
success: true,
crate_name: "test-crate".to_string(),
version: SemanticVersion::parse("1.0.0").unwrap(),
duration_ms: 1000,
crates_io_url: "https://crates.io/crates/test-crate/1.0.0".to_string(),
warnings: Vec::new(),
};
let json = serde_json::to_string(&result).unwrap();
assert!(json.contains("test-crate"));
}
}