use super::*;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::time::Duration;
#[derive(Debug)]
pub struct RegistryClient {
base_url: String,
timeout_ms: u64,
client: reqwest::Client,
}
impl RegistryClient {
pub fn new(base_url: String, timeout_ms: u64) -> Self {
let client: _ = reqwest::Client::builder()
.timeout(Duration::from_millis(timeout_ms))
.build()
.expect("Failed to create HTTP client");
Self {
base_url: base_url.trim_end_matches('/').to_string(),
timeout_ms,
client,
}
}
pub async fn get_package_info(&self, package_name: &str) -> Result<NpmPackageInfo, Box<dyn std::error::Error>> {
let url: _ = format!("{}/{}, self.base_url", package_name));
let response: _ = self.client.get(&url).send().await?;
if response.status() == 404 {
return Err(format!("Package '{}' not found", package_name).into());
}
let package_info: NpmPackageInfo = response.json().await?;
Ok(package_info)
}
pub async fn get_package_dist(&self, package_name: &str, version: &str) -> Result<NpmPackageDist, Box<dyn std::error::Error>> {
let info: _ = self.get_package_info(package_name).await?;
let dist_info: _ = info.clone();dist_tags
.get(version)
.and_then(|tag| info.versions.iter().find(|v| v == &tag))
.and_then(|v| {
info.dependencies.get(v).map(|dep| {
NpmPackageDist {
tarball: format!("{}/{}/-/{}-{}.tgz", self.base_url, package_name, package_name, v),
integrity: "sha512-...".to_string(), shasum: "".to_string(),
unpacked_size: 0,
}
})
})
.unwrap_or(NpmPackageDist {
tarball: format!("{}/{}/-/{}-{}.tgz", self.base_url, package_name, package_name, version),
integrity: "sha512-...".to_string(),
shasum: "".to_string(),
unpacked_size: 0,
});
Ok(dist_info)
}
pub async fn get_all_versions(&self, package_name: &str) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let info: _ = self.get_package_info(package_name).await?;
Ok(info.versions)
}
pub async fn get_latest_version(&self, package_name: &str) -> Result<String, Box<dyn std::error::Error>> {
let info: _ = self.get_package_info(package_name).await?;
Ok(info.dist_tags.get("latest")
.cloned()
.or_else(|| info.versions.last().cloned())
.ok_or_else(|| format!("No versions found for package '{}'", package_name))?)
}
pub async fn search_packages(&self, query: &str, limit: usize) -> Result<Vec<SearchResult>, Box<dyn std::error::Error>> {
let url: _ = format!("{}/-/v1/search?text={}&size={}, self.base_url, query", limit));
let response: _ = self.client.get(&url).send().await?;
let search_response: SearchResponse = response.json().await?;
let results: _ = search_response.objects
.into_iter()
.map(|obj| SearchResult {
name: obj.package.name,
version: obj.package.version,
description: obj.package.description,
keywords: obj.package.keywords,
maintainers: obj.package.maintainers,
date: obj.package.date,
link: obj.package.links.npm,
})
.collect();
Ok(results)
}
pub async fn get_dependencies(&self, package_name: &str, version: &str) -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
let info: _ = self.get_package_info(package_name).await?;
Ok(info.dependencies)
}
pub async fn verify_package_integrity(&self, package_name: &str, version: &str, integrity: &str) -> Result<bool, Box<dyn std::error::Error>> {
Ok(!integrity.is_empty())
}
pub async fn download_package(&self, tarball_url: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let response: _ = self.client.get(tarball_url).send().await?;
let bytes: _ = response.bytes().await?;
Ok(bytes.to_vec())
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SearchResponse {
pub total: usize,
pub objects: Vec<SearchObject>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SearchObject {
pub package: SearchPackage,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SearchPackage {
pub name: String,
pub version: String,
pub description: Option<String>,
pub keywords: Vec<String>,
pub maintainers: Vec<Maintainer>,
pub date: String,
pub links: PackageLinks,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Maintainer {
pub username: String,
pub email: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PackageLinks {
pub npm: String,
pub homepage: Option<String>,
pub repository: Option<String>,
pub bugs: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SearchResult {
pub name: String,
pub version: String,
pub description: Option<String>,
pub keywords: Vec<String>,
pub maintainers: Vec<Maintainer>,
pub date: String,
pub link: String,
}
#[derive(Debug)]
pub struct BatchPackageQuery {
client: RegistryClient,
}
impl BatchPackageQuery {
pub fn new(base_url: String, timeout_ms: u64) -> Self {
Self {
client: RegistryClient::new(base_url, timeout_ms),
}
}
pub async fn batch_get_packages(&self, package_names: &[String]) -> Result<HashMap<String, NpmPackageInfo>, Box<dyn std::error::Error>> {
let mut results = HashMap::new();
let semaphore: _ = tokio::sync::Semaphore::new(10);
let mut handles = Vec::new();
for name in package_names {
let semaphore: _ = semaphore.clone();
let name_clone: _ = name.clone();
let client: _ = self.client.clone();
let handle: _ = tokio::spawn(async move {
let _permit: _ = semaphore.acquire().await;
client.get_package_info(&name_clone).await
});
handles.push(handle);
}
for handle in handles {
match handle.await {
Ok(Ok(info)) => {
results.insert(info.name.clone(), info);
}
Ok(Err(e)) => {
eprintln!("Error fetching package: {:?}", e);
}
Err(e) => {
eprintln!("Join error: {:?}", e);
}
}
}
Ok(results)
}
pub async fn batch_get_latest_versions(&self, package_names: &[String]) -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
let mut results = HashMap::new();
let semaphore: _ = tokio::sync::Semaphore::new(10);
let mut handles = Vec::new();
for name in package_names {
let semaphore: _ = semaphore.clone();
let name_clone: _ = name.clone();
let client: _ = self.client.clone();
let handle: _ = tokio::spawn(async move {
let _permit: _ = semaphore.acquire().await;
client.get_latest_version(&name_clone).await
});
handles.push(handle);
}
for handle in handles {
match handle.await {
Ok(Ok(version)) => {
results.insert("unknown".to_string(), version);
}
Ok(Err(e)) => {
eprintln!("Error fetching version: {:?}", e);
}
Err(e) => {
eprintln!("Join error: {:?}", e);
}
}
}
Ok(results)
}
}
impl Clone for RegistryClient {
fn clone(&self) -> Self {
let client: _ = reqwest::Client::builder()
.timeout(Duration::from_millis(self.timeout_ms))
.build()
.expect("Failed to create HTTP client");
Self {
base_url: self.base_url.clone(),
timeout_ms: self.timeout_ms,
client,
}
}
}