Replace dep crates_io_api with in-house solution (#846)

It also uses `max_stable_version` in the json downloaded from https://crates.io/api/v1/crates/$name if possible, which is equivalent to the version shown on https://crates.io/crates/$name .

 - Add new feat `json` to `binstalk-downloader`
 - Impl new async fn `Response::json`
 - use `Response::json` in `GhApiClient` impl
 - Mark all err types in binstalk-downloader as `non_exhaustive`
 - Ret `remote::Error` in `remote::Certificate::{from_pem, from_der}` instead of `ReqwestError`.
 - Refactor `BinstallError`: Merge variant `Unzip`, `Reqwest` & `Http`
    into one variant `Download`.
 - Manually download and parse json from httos://crates.io/api/v1
 - Remove unused deps `crates_io_api`

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
Jiahao XU 2023-03-02 18:25:34 +11:00 committed by GitHub
parent c00d648dac
commit 8eee318ccd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 120 additions and 244 deletions

View file

@ -1,8 +1,9 @@
use std::path::PathBuf;
use cargo_toml::Manifest;
use crates_io_api::AsyncClient;
use compact_str::CompactString;
use semver::VersionReq;
use serde::Deserialize;
use tracing::debug;
use crate::{
@ -14,43 +15,98 @@ use crate::{
manifests::cargo_toml_binstall::{Meta, TarBasedFmt},
};
use super::find_version;
mod vfs;
mod visitor;
use visitor::ManifestVisitor;
/// Fetch a crate Cargo.toml by name and version from crates.io
#[derive(Deserialize)]
struct CrateInfo {
#[serde(rename = "crate")]
inner: CrateInfoInner,
}
#[derive(Deserialize)]
struct CrateInfoInner {
max_stable_version: CompactString,
}
#[derive(Deserialize)]
struct Versions {
versions: Vec<Version>,
}
#[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,
crates_io_api_client: &AsyncClient,
name: &str,
version_req: &VersionReq,
) -> Result<Manifest<Meta>, BinstallError> {
// Fetch / update index
debug!("Looking up crate information");
// Fetch online crate information
let base_info = crates_io_api_client.get_crate(name).await.map_err(|err| {
Box::new(CratesIoApiError {
crate_name: name.into(),
err,
})
})?;
let response = client
.get(Url::parse(&format!(
"https://crates.io/api/v1/crates/{name}"
))?)
.send(true)
.await
.map_err(|err| {
BinstallError::CratesIoApi(Box::new(CratesIoApiError {
crate_name: name.into(),
err,
}))
})?;
// Locate matching version
let version_iter = base_info.versions.iter().filter(|v| !v.yanked);
let (version, version_name) = find_version(version_req, version_iter)?;
let version = if version_req == &VersionReq::STAR {
let crate_info: CrateInfo = response.json().await?;
crate_info.inner.max_stable_version
} else {
let response: Versions = response.json().await?;
response
.versions
.into_iter()
.filter_map(|item| {
if !item.yanked {
// Remove leading `v` for git tags
let num = if let Some(num) = item.num.strip_prefix('v') {
num.into()
} else {
item.num
};
debug!("Found information for crate version: '{}'", version.num);
// Parse out version
let ver = semver::Version::parse(&num).ok()?;
// Filter by version match
version_req.matches(&ver).then_some((num, ver))
} else {
None
}
})
// Return highest version
.max_by(|(_ver_str_x, ver_x), (_ver_str_y, ver_y)| ver_x.cmp(ver_y))
.ok_or_else(|| BinstallError::VersionMismatch {
req: version_req.clone(),
})?
.0
};
debug!("Found information for crate version: '{version}'");
// Download crate to temporary dir (crates.io or git?)
let crate_url = format!("https://crates.io/{}", version.dl_path);
let crate_url = format!("https://crates.io/api/v1/crates/{name}/{version}/download");
debug!("Fetching crate from: {crate_url} and extracting Cargo.toml from it");
let manifest_dir_path: PathBuf = format!("{name}-{version_name}").into();
let manifest_dir_path: PathBuf = format!("{name}-{version}").into();
let mut manifest_visitor = ManifestVisitor::new(manifest_dir_path);

View file

@ -1,46 +0,0 @@
use semver::VersionReq;
use crate::errors::BinstallError;
pub(super) trait Version {
/// Return `None` on error.
fn get_version(&self) -> Option<semver::Version>;
}
impl<T: Version> Version for &T {
fn get_version(&self) -> Option<semver::Version> {
(*self).get_version()
}
}
impl Version for crates_io_api::Version {
fn get_version(&self) -> Option<semver::Version> {
// Remove leading `v` for git tags
let ver_str = match self.num.strip_prefix('v') {
Some(v) => v,
None => &self.num,
};
// Parse out version
semver::Version::parse(ver_str).ok()
}
}
pub(super) fn find_version<Item: Version, VersionIter: Iterator<Item = Item>>(
version_req: &VersionReq,
version_iter: VersionIter,
) -> Result<(Item, semver::Version), BinstallError> {
version_iter
// Filter for matching versions
.filter_map(|item| {
let ver = item.get_version()?;
// Filter by version match
version_req.matches(&ver).then_some((item, ver))
})
// Return highest version
.max_by(|(_item_x, ver_x), (_item_y, ver_y)| ver_x.cmp(ver_y))
.ok_or_else(|| BinstallError::VersionMismatch {
req: version_req.clone(),
})
}