cargo_client 0.13.0

Cargo registry (crates.io) client
Documentation
use crate::{CargoClient, ExtendedCrate, GetCrateError, GetCrateRequestView};
use async_trait::async_trait;
use reqwest::{Client, StatusCode};
use url::Url;

pub struct HttpCargoClient {
  client: Client,
  server: Url,
}

impl HttpCargoClient {
  pub fn new() -> Self {
    Self {
      client: Client::builder()
        .user_agent("cargo_client")
        .build()
        .expect("building the client always succeeds"),
      server: Url::parse("https://crates.io/").expect("the crates.io URL is well-formed"),
    }
  }

  pub fn api_url<I>(&self, segments: I) -> Url
  where
    I: IntoIterator,
    I::Item: AsRef<str>,
  {
    let mut res = self.server.clone();
    {
      let mut p = res.path_segments_mut().expect("Cargo registry URL has path segments");
      p.extend(["api", "v1"]);
      p.extend(segments);
    }
    res
  }
}

impl Default for HttpCargoClient {
  fn default() -> Self {
    Self::new()
  }
}

#[async_trait]
impl CargoClient for HttpCargoClient {
  async fn get_crate(&self, req: GetCrateRequestView<'_>) -> Result<ExtendedCrate, GetCrateError> {
    let url = self.api_url(["crates", req.name]);
    let res = self
      .client
      .get(url)
      .send()
      .await
      .map_err(|e| GetCrateError::Send(format!("{e:?}")))?;
    match res.status() {
      StatusCode::OK => {
        let body: ExtendedCrate = res.json().await.map_err(|e| GetCrateError::Receive(format!("{e:?}")))?;
        Ok(body)
      }
      StatusCode::NOT_FOUND => Err(GetCrateError::NotFound),
      code => Err(GetCrateError::Receive(format!("unexpected status code {code}"))),
    }
  }
}