blob: 8f032c0ca40d52aad737963969793f1cf74a2c89 [file] [log] [blame]
// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crates_index::{Crate, SparseIndex};
use std::{cell::RefCell, collections::HashSet, process::Command};
use success_or_error::RunAndExpectSuccess;
use crate::Error;
/// A local index of crates.io data, with a way of updating the index
/// by fetching data from crates.io.
pub struct CratesIoIndex {
fetcher: Box<dyn CratesIoFetcher>,
}
impl CratesIoIndex {
/// Constructs a CratesIoIndex that uses the `ureq` crate
/// to fetch data from crates.io via https.
#[cfg(feature = "ureq")]
pub fn new_ureq() -> Result<CratesIoIndex, Error> {
Ok(CratesIoIndex {
fetcher: Box::new(UreqFetcher {
index: crates_index::SparseIndex::new_cargo_default()?,
agent: ureq::Agent::new_with_defaults(),
fetched: RefCell::new(HashSet::new()),
}),
})
}
/// Constructs a CratesIoIndex in offline mode. No new data is
/// fetched from crates.io, but data already in the local cache
/// is returned.
pub fn new_offline() -> Result<CratesIoIndex, Error> {
Ok(CratesIoIndex {
fetcher: Box::new(OfflineFetcher {
index: crates_index::SparseIndex::new_cargo_default()?,
}),
})
}
/// Constructs a CratesIoIndex that uses cargo commands to fetch data
/// from crates.io. Less efficient than making direct https requests,
/// but avoids complicated dependencies on cryptographic crates.
pub fn new_cargo() -> Result<CratesIoIndex, Error> {
Ok(CratesIoIndex {
fetcher: Box::new(CargoFetcher {
index: crates_index::SparseIndex::new_cargo_default()?,
fetched: RefCell::new(HashSet::new()),
}),
})
}
/// Fetches and returns the crates.io data on a crate.
pub fn get_crate(&self, crate_name: impl AsRef<str>) -> Result<Crate, Error> {
self.fetcher.fetch(crate_name.as_ref())
}
}
pub trait CratesIoFetcher {
fn fetch(&self, crate_name: &str) -> Result<Crate, Error>;
}
#[cfg(feature = "ureq")]
struct UreqFetcher {
index: SparseIndex,
agent: ureq::Agent,
// Keep track of crates we have fetched, to avoid fetching them multiple times.
fetched: RefCell<HashSet<String>>,
}
#[cfg(feature = "ureq")]
impl CratesIoFetcher for UreqFetcher {
fn fetch(&self, crate_name: &str) -> Result<Crate, Error> {
let mut fetched = self.fetched.borrow_mut();
if fetched.contains(crate_name) {
return Ok(self.index.crate_from_cache(crate_name.as_ref())?);
}
let request = self
.index
.make_cache_request(crate_name)?
.version(ureq::http::Version::HTTP_11)
.body(())?;
let response = self.agent.run(request)?;
let (parts, mut body) = response.into_parts();
let response = crates_index::http::Response::from_parts(parts, body.read_to_vec()?);
let response = self
.index
.parse_cache_response(crate_name, response, true)?
.ok_or(Error::CrateNotFound(crate_name.to_string()))?;
fetched.insert(crate_name.to_string());
Ok(response)
}
}
struct OfflineFetcher {
index: SparseIndex,
}
impl CratesIoFetcher for OfflineFetcher {
fn fetch(&self, crate_name: &str) -> Result<Crate, Error> {
Ok(self.index.crate_from_cache(crate_name.as_ref())?)
}
}
// A fetcher that uses cargo commands to update the index. This
// lets us avoid complicated dependencies on crates that implement
// https.
struct CargoFetcher {
index: SparseIndex,
// Keep track of crates we have fetched, to avoid fetching them multiple times.
fetched: RefCell<HashSet<String>>,
}
impl CratesIoFetcher for CargoFetcher {
fn fetch(&self, crate_name: &str) -> Result<Crate, Error> {
let mut fetched = self.fetched.borrow_mut();
if !fetched.contains(crate_name) {
let tempdir = tempfile::tempdir()?;
Command::new("cargo")
.args(["init", "--lib", "--name", "fake-crate"])
.current_dir(&tempdir)
.run_quiet_and_expect_success()?;
Command::new("cargo")
.args(["add", crate_name])
.current_dir(&tempdir)
.run_quiet_and_expect_success()?;
Command::new("cargo")
.args(["update", crate_name])
.current_dir(&tempdir)
.run_quiet_and_expect_success()?;
fetched.insert(crate_name.to_string());
}
Ok(self.index.crate_from_cache(crate_name.as_ref())?)
}
}