use anyhow::{Context, Result};
use reqwest::Client;
use super::package::{PackageMetadata, VersionInfo};
const NPM_REGISTRY: &str = "https://registry.npmjs.org";
fn http_client() -> Result<Client> {
Client::builder()
.user_agent("aegis-cli/0.1.0")
.build()
.context("failed to build HTTP client")
}
fn encode_package_name(name: &str) -> String {
name.replace('/', "%2F")
}
pub async fn fetch_package_metadata(name: &str, version: Option<&str>) -> Result<PackageMetadata> {
let client = http_client()?;
match version {
Some(v) => fetch_version_as_metadata(&client, name, v).await,
None => fetch_full_metadata(&client, name).await,
}
}
async fn fetch_full_metadata(client: &Client, name: &str) -> Result<PackageMetadata> {
let url = format!("{}/{}", NPM_REGISTRY, name);
tracing::info!(package = %name, "fetching full package metadata");
tracing::debug!(url = %url);
let response = client
.get(&url)
.send()
.await
.with_context(|| format!("HTTP request failed for package '{name}'"))?;
if !response.status().is_success() {
anyhow::bail!(
"npm registry returned {} for package '{}'",
response.status(),
name
);
}
let metadata: PackageMetadata = response
.json()
.await
.with_context(|| format!("failed to parse registry JSON for package '{name}'"))?;
tracing::info!(
package = %name,
versions = metadata.versions.len(),
"successfully fetched metadata"
);
Ok(metadata)
}
async fn fetch_version_as_metadata(
client: &Client,
name: &str,
version: &str,
) -> Result<PackageMetadata> {
let encoded = encode_package_name(name);
let url = format!("{}/{}/{}", NPM_REGISTRY, encoded, version);
tracing::info!(package = %name, version = %version, "fetching version metadata");
tracing::debug!(url = %url);
let response = client
.get(&url)
.send()
.await
.with_context(|| format!("HTTP request failed for '{name}@{version}'"))?;
if !response.status().is_success() {
anyhow::bail!(
"npm registry returned {} for '{}@{}'",
response.status(),
name,
version
);
}
let version_info: VersionInfo = response
.json()
.await
.with_context(|| format!("failed to parse registry JSON for '{name}@{version}'"))?;
let ver_string = version_info
.version
.clone()
.unwrap_or_else(|| version.to_string());
let mut versions = std::collections::HashMap::new();
versions.insert(ver_string.clone(), version_info);
let mut dist_tags = std::collections::HashMap::new();
dist_tags.insert("latest".to_string(), ver_string);
Ok(PackageMetadata {
name: Some(name.to_string()),
description: None,
versions,
time: std::collections::HashMap::new(),
maintainers: None,
dist_tags: Some(dist_tags),
extra: std::collections::HashMap::new(),
})
}