From e96477a1162023eaed2daae71354c2ec756e5220 Mon Sep 17 00:00:00 2001 From: Jiahao XU Date: Thu, 25 May 2023 22:56:31 +1000 Subject: [PATCH] Optimize `fetch_crate_cratesio` for exact version (#1089) If `version_req` requests a specific version instead of a range, then there is no need to pull all versions available from https://crates.io/api/v1/crates Signed-off-by: Jiahao XU --- crates/binstalk/src/drivers/crates_io.rs | 132 +++++++++++++++++------ 1 file changed, 102 insertions(+), 30 deletions(-) diff --git a/crates/binstalk/src/drivers/crates_io.rs b/crates/binstalk/src/drivers/crates_io.rs index 28816597..441a44a1 100644 --- a/crates/binstalk/src/drivers/crates_io.rs +++ b/crates/binstalk/src/drivers/crates_io.rs @@ -1,8 +1,8 @@ use std::path::PathBuf; use cargo_toml::Manifest; -use compact_str::CompactString; -use semver::VersionReq; +use compact_str::{CompactString, ToCompactString}; +use semver::{Comparator, Op as ComparatorOp, Version as SemVersion, VersionReq}; use serde::Deserialize; use tracing::debug; @@ -21,38 +21,68 @@ mod vfs; mod visitor; use visitor::ManifestVisitor; -#[derive(Deserialize)] -struct CrateInfo { - #[serde(rename = "crate")] - inner: CrateInfoInner, +async fn is_crate_yanked( + client: &Client, + name: &str, + version: &str, +) -> Result { + #[derive(Deserialize)] + struct CrateInfo { + version: Inner, + } + + #[derive(Deserialize)] + struct Inner { + yanked: bool, + } + + // Fetch / update index + debug!("Looking up crate information"); + + let response = client + .get(Url::parse(&format!( + "https://crates.io/api/v1/crates/{name}/{version}" + ))?) + .send(true) + .await + .map_err(|err| { + BinstallError::CratesIoApi(Box::new(CratesIoApiError { + crate_name: name.into(), + err, + })) + })?; + + let info: CrateInfo = response.json().await?; + + Ok(info.version.yanked) } -#[derive(Deserialize)] -struct CrateInfoInner { - max_stable_version: CompactString, -} - -#[derive(Deserialize)] -struct Versions { - versions: Vec, -} - -#[derive(Deserialize)] -struct Version { - num: CompactString, - yanked: bool, -} - -/// Find the crate by name, get its latest stable version matches `version_req`, -/// retrieve its Cargo.toml and infer all its bins. -pub async fn fetch_crate_cratesio( - client: Client, +async fn fetch_crate_cratesio_version_matched( + client: &Client, name: &str, version_req: &VersionReq, - crates_io_rate_limit: &CratesIoRateLimit, -) -> Result, BinstallError> { - // Wait until we can make another request to crates.io - crates_io_rate_limit.tick().await; +) -> Result { + #[derive(Deserialize)] + struct CrateInfo { + #[serde(rename = "crate")] + inner: CrateInfoInner, + } + + #[derive(Deserialize)] + struct CrateInfoInner { + max_stable_version: CompactString, + } + + #[derive(Deserialize)] + struct Versions { + versions: Vec, + } + + #[derive(Deserialize)] + struct Version { + num: CompactString, + yanked: bool, + } // Fetch / update index debug!("Looking up crate information"); @@ -106,6 +136,48 @@ pub async fn fetch_crate_cratesio( debug!("Found information for crate version: '{version}'"); + Ok(version) +} + +/// Find the crate by name, get its latest stable version matches `version_req`, +/// retrieve its Cargo.toml and infer all its bins. +pub async fn fetch_crate_cratesio( + client: Client, + name: &str, + version_req: &VersionReq, + crates_io_rate_limit: &CratesIoRateLimit, +) -> Result, BinstallError> { + // Wait until we can make another request to crates.io + crates_io_rate_limit.tick().await; + + let version = match version_req.comparators.as_slice() { + [Comparator { + op: ComparatorOp::Exact, + major, + minor: Some(minor), + patch: Some(patch), + pre, + }] => { + let version = SemVersion { + major: *major, + minor: *minor, + patch: *patch, + pre: pre.clone(), + build: Default::default(), + } + .to_compact_string(); + + if is_crate_yanked(&client, name, &version).await? { + return Err(BinstallError::VersionMismatch { + req: version_req.clone(), + }); + } + + version + } + _ => fetch_crate_cratesio_version_matched(&client, name, version_req).await?, + }; + // Download crate to temporary dir (crates.io or git?) let crate_url = format!("https://crates.io/api/v1/crates/{name}/{version}/download");