Skip to content

Commit

Permalink
fix: Unify + fix URL construction
Browse files Browse the repository at this point in the history
* Unify url construction with helper methods that are shared between
sync and async
* Make sure to URL encode the crate name by adding it as a manual path
segment
* Manually handle the case where the crate name has a slash, because the
API returns nonsensical errors, even if the crate name is properly URL
encoded
("invalid semver")
  • Loading branch information
theduke committed Jan 29, 2022
1 parent 9f00481 commit 8472aaa
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 47 deletions.
100 changes: 71 additions & 29 deletions src/async_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,26 +187,21 @@ impl Client {
/// Retrieve information of a crate.
///
/// If you require detailed information, consider using [full_crate]().
pub async fn get_crate(&self, name: &str) -> Result<CrateResponse, Error> {
let url = self.base_url.join("crates/").unwrap().join(name).unwrap();
pub async fn get_crate(&self, crate_name: &str) -> Result<CrateResponse, Error> {
let url = build_crate_url(&self.base_url, crate_name)?;

self.get(&url).await
}

/// Retrieve download stats for a crate.
pub async fn crate_downloads(&self, name: &str) -> Result<CrateDownloads, Error> {
let url = self
.base_url
.join(&format!("crates/{}/downloads", name))
.unwrap();
pub async fn crate_downloads(&self, crate_name: &str) -> Result<CrateDownloads, Error> {
let url = build_crate_downloads_url(&self.base_url, crate_name)?;
self.get(&url).await
}

/// Retrieve the owners of a crate.
pub async fn crate_owners(&self, name: &str) -> Result<Vec<User>, Error> {
let url = self
.base_url
.join(&format!("crates/{}/owners", name))
.unwrap();
let url = build_crate_owners_url(&self.base_url, name)?;
self.get::<Owners>(&url).await.map(|data| data.users)
}

Expand All @@ -221,14 +216,7 @@ impl Client {
// If page is zero, bump it to 1.
let page = page.max(1);

let url = self
.base_url
.join(&format!(
"crates/{0}/reverse_dependencies?per_page=100&page={1}",
crate_name, page
))
.unwrap();

let url = build_crate_reverse_deps_url(&self.base_url, crate_name, page)?;
let page = self.get::<ReverseDependenciesAsReceived>(&url).await?;

let mut deps = ReverseDependencies {
Expand Down Expand Up @@ -275,11 +263,8 @@ impl Client {
}

/// Retrieve the authors for a crate version.
pub async fn crate_authors(&self, name: &str, version: &str) -> Result<Authors, Error> {
let url = self
.base_url
.join(&format!("crates/{}/{}/authors", name, version))
.unwrap();
pub async fn crate_authors(&self, crate_name: &str, version: &str) -> Result<Authors, Error> {
let url = build_crate_authors_url(&self.base_url, crate_name, version)?;
self.get::<AuthorsResponse>(&url).await.map(|res| Authors {
names: res.meta.names,
})
Expand All @@ -288,13 +273,10 @@ impl Client {
/// Retrieve the dependencies of a crate version.
pub async fn crate_dependencies(
&self,
name: &str,
crate_name: &str,
version: &str,
) -> Result<Vec<Dependency>, Error> {
let url = self
.base_url
.join(&format!("crates/{}/{}/dependencies", name, version))
.unwrap();
let url = build_crate_dependencies_url(&self.base_url, crate_name, version)?;
self.get::<Dependencies>(&url)
.await
.map(|res| res.dependencies)
Expand Down Expand Up @@ -396,6 +378,65 @@ impl Client {
}
}

pub(crate) fn build_crate_url(base: &Url, crate_name: &str) -> Result<Url, Error> {
let mut url = base.join("crates/")?;
url.path_segments_mut().unwrap().push(crate_name);

// Guard against slashes in the crate name.
// The API returns a nonsensical error in this case.
if crate_name.contains('/') {
Err(Error::NotFound(crate::error::NotFound {
url: url.to_string(),
}))
} else {
Ok(url)
}
}

pub(crate) fn build_crate_downloads_url(base: &Url, crate_name: &str) -> Result<Url, Error> {
build_crate_url(base, crate_name)?
.join("downloads")
.map_err(Error::from)
}

pub(crate) fn build_crate_owners_url(base: &Url, crate_name: &str) -> Result<Url, Error> {
build_crate_url(base, crate_name)?
.join("owners")
.map_err(Error::from)
}

pub(crate) fn build_crate_reverse_deps_url(
base: &Url,
crate_name: &str,
page: u64,
) -> Result<Url, Error> {
build_crate_url(base, crate_name)?
.join(&format!("reverse_dependencies?per_page=100&page={page}"))
.map_err(Error::from)
}

pub(crate) fn build_crate_authors_url(
base: &Url,
crate_name: &str,
version: &str,
) -> Result<Url, Error> {
build_crate_url(base, crate_name)?
.join(version)?
.join("authors")
.map_err(Error::from)
}

pub(crate) fn build_crate_dependencies_url(
base: &Url,
crate_name: &str,
version: &str,
) -> Result<Url, Error> {
build_crate_url(base, crate_name)?
.join(version)?
.join("dependencies")
.map_err(Error::from)
}

#[cfg(test)]
mod test {
use super::*;
Expand Down Expand Up @@ -512,4 +553,5 @@ mod test {

Ok(())
}

}
36 changes: 18 additions & 18 deletions src/sync_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,20 +103,20 @@ impl SyncClient {
/// Retrieve information of a crate.
///
/// If you require detailed information, consider using [full_crate]().
pub fn get_crate(&self, name: &str) -> Result<CrateResponse, Error> {
let url = self.base_url.join("crates/")?.join(name)?;
pub fn get_crate(&self, crate_name: &str) -> Result<CrateResponse, Error> {
let url = super::async_client::build_crate_url(&self.base_url, crate_name)?;
self.get(url)
}

/// Retrieve download stats for a crate.
pub fn crate_downloads(&self, name: &str) -> Result<CrateDownloads, Error> {
let url = self.base_url.join(&format!("crates/{}/downloads", name))?;
pub fn crate_downloads(&self, crate_name: &str) -> Result<CrateDownloads, Error> {
let url = super::async_client::build_crate_downloads_url(&self.base_url, crate_name)?;
self.get(url)
}

/// Retrieve the owners of a crate.
pub fn crate_owners(&self, name: &str) -> Result<Vec<User>, Error> {
let url = self.base_url.join(&format!("crates/{}/owners", name))?;
pub fn crate_owners(&self, crate_name: &str) -> Result<Vec<User>, Error> {
let url = super::async_client::build_crate_owners_url(&self.base_url, crate_name)?;
let resp: Owners = self.get(url)?;
Ok(resp.users)
}
Expand All @@ -126,10 +126,8 @@ impl SyncClient {
crate_name: &str,
page: u64,
) -> Result<ReverseDependenciesAsReceived, Error> {
let url = self.base_url.join(&format!(
"crates/{}/reverse_dependencies?per_page=100&page={}",
crate_name, page
))?;
let url =
super::async_client::build_crate_reverse_deps_url(&self.base_url, crate_name, page)?;
self.get(url)
}

Expand Down Expand Up @@ -165,21 +163,23 @@ impl SyncClient {
}

/// Retrieve the authors for a crate version.
pub fn crate_authors(&self, name: &str, version: &str) -> Result<Authors, Error> {
let url = self
.base_url
.join(&format!("crates/{}/{}/authors", name, version))?;
pub fn crate_authors(&self, crate_name: &str, version: &str) -> Result<Authors, Error> {
let url =
super::async_client::build_crate_authors_url(&self.base_url, crate_name, version)?;
let res: AuthorsResponse = self.get(url)?;
Ok(Authors {
names: res.meta.names,
})
}

/// Retrieve the dependencies of a crate version.
pub fn crate_dependencies(&self, name: &str, version: &str) -> Result<Vec<Dependency>, Error> {
let url = self
.base_url
.join(&format!("crates/{}/{}/dependencies", name, version))?;
pub fn crate_dependencies(
&self,
crate_name: &str,
version: &str,
) -> Result<Vec<Dependency>, Error> {
let url =
super::async_client::build_crate_dependencies_url(&self.base_url, crate_name, version)?;
let resp: Dependencies = self.get(url)?;
Ok(resp.dependencies)
}
Expand Down

0 comments on commit 8472aaa

Please sign in to comment.