diff --git a/Cargo.toml b/Cargo.toml index d52f5e8..ad36737 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,30 +1,33 @@ [package] -authors = ["theduke "] -name = "crates_io_api" -description = "API client for crates.io" +authors = ["theduke ", "jonaspleyer >>, + rate_limit: Duration, + last_request_time: std::sync::Arc>>, base_url: Url, } +#[cfg(not(target_arch = "wasm32"))] +#[cfg_attr(docsrs, doc(cfg(not(target_arch = "wasm32"))))] pub struct CrateStream { client: Client, filter: CratesQuery, @@ -28,6 +36,8 @@ pub struct CrateStream { next_page_fetch: Option>>, } +#[cfg(not(target_arch = "wasm32"))] +#[cfg_attr(docsrs, doc(cfg(not(target_arch = "wasm32"))))] impl CrateStream { fn new(client: Client, filter: CratesQuery) -> Self { Self { @@ -40,6 +50,8 @@ impl CrateStream { } } +#[cfg(not(target_arch = "wasm32"))] +#[cfg_attr(docsrs, doc(cfg(not(target_arch = "wasm32"))))] impl futures::stream::Stream for CrateStream { type Item = Result; @@ -112,17 +124,18 @@ impl Client { /// Example user agent: `"my_bot (my_bot.com/info)"` or `"my_bot (help@my_bot.com)"`. /// /// ```rust + /// # use web_time::Duration; /// # fn f() -> Result<(), Box> { /// let client = crates_io_api::AsyncClient::new( /// "my_bot (help@my_bot.com)", - /// std::time::Duration::from_millis(1000), + /// Duration::from_millis(1000), /// ).unwrap(); /// # Ok(()) /// # } /// ``` pub fn new( user_agent: &str, - rate_limit: std::time::Duration, + rate_limit: Duration, ) -> Result { let mut headers = header::HeaderMap::new(); headers.insert( @@ -146,7 +159,7 @@ impl Client { /// At most one request will be executed in the specified duration. /// The guidelines suggest 1 per second or less. /// (Only one request is executed concurrenly, even if the given Duration is 0). - pub fn with_http_client(client: HttpClient, rate_limit: std::time::Duration) -> Self { + pub fn with_http_client(client: HttpClient, rate_limit: Duration) -> Self { let limiter = std::sync::Arc::new(tokio::sync::Mutex::new(None)); Self { @@ -166,7 +179,7 @@ impl Client { } } - let time = tokio::time::Instant::now(); + let time = web_time::Instant::now(); let res = self.client.get(url.clone()).send().await?; if !res.status().is_success() { @@ -380,6 +393,8 @@ impl Client { } /// Get a stream over all crates matching the given [`CratesQuery`]. + #[cfg(not(target_arch = "wasm32"))] + #[cfg_attr(docsrs, doc(cfg(not(target_arch = "wasm32"))))] pub fn crates_stream(&self, filter: CratesQuery) -> CrateStream { CrateStream::new(self.clone(), filter) } @@ -391,78 +406,6 @@ impl Client { } } -pub(crate) fn build_crate_url(base: &Url, crate_name: &str) -> Result { - 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::NotFoundError { - url: url.to_string(), - })) - } else { - Ok(url) - } -} - -fn build_crate_url_nested(base: &Url, crate_name: &str) -> Result { - let mut url = base.join("crates")?; - url.path_segments_mut().unwrap().push(crate_name).push("/"); - - // 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::NotFoundError { - url: url.to_string(), - })) - } else { - Ok(url) - } -} - -pub(crate) fn build_crate_downloads_url(base: &Url, crate_name: &str) -> Result { - build_crate_url_nested(base, crate_name)? - .join("downloads") - .map_err(Error::from) -} - -pub(crate) fn build_crate_owners_url(base: &Url, crate_name: &str) -> Result { - build_crate_url_nested(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 { - build_crate_url_nested(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 { - build_crate_url_nested(base, crate_name)? - .join(&format!("{version}/authors")) - .map_err(Error::from) -} - -pub(crate) fn build_crate_dependencies_url( - base: &Url, - crate_name: &str, - version: &str, -) -> Result { - build_crate_url_nested(base, crate_name)? - .join(&format!("{version}/dependencies")) - .map_err(Error::from) -} - #[cfg(test)] mod test { use super::*; @@ -470,7 +413,7 @@ mod test { fn build_test_client() -> Client { Client::new( "crates-io-api-continuous-integration (github.com/theduke/crates-io-api)", - std::time::Duration::from_millis(1000), + web_time::Duration::from_millis(1000), ) .unwrap() } diff --git a/src/lib.rs b/src/lib.rs index b304da1..453139e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,15 +43,22 @@ #![recursion_limit = "128"] #![deny(missing_docs)] +#![cfg_attr(docsrs, feature(doc_cfg))] mod async_client; mod error; +#[cfg(not(target_arch = "wasm32"))] +#[cfg_attr(docsrs, doc(cfg(not(target_arch = "wasm32"))))] mod sync_client; mod types; +mod util; pub use crate::{ async_client::Client as AsyncClient, error::{Error, NotFoundError, PermissionDeniedError}, - sync_client::SyncClient, types::*, }; + +#[cfg(not(target_arch = "wasm32"))] +#[cfg_attr(docsrs, doc(cfg(not(target_arch = "wasm32"))))] +pub use crate::sync_client::SyncClient; diff --git a/src/sync_client.rs b/src/sync_client.rs index fd2ca44..8bc602f 100644 --- a/src/sync_client.rs +++ b/src/sync_client.rs @@ -113,19 +113,19 @@ impl SyncClient { /// /// If you require detailed information, consider using [full_crate](). pub fn get_crate(&self, crate_name: &str) -> Result { - let url = super::async_client::build_crate_url(&self.base_url, crate_name)?; + let url = super::util::build_crate_url(&self.base_url, crate_name)?; self.get(url) } /// Retrieve download stats for a crate. pub fn crate_downloads(&self, crate_name: &str) -> Result { - let url = super::async_client::build_crate_downloads_url(&self.base_url, crate_name)?; + let url = super::util::build_crate_downloads_url(&self.base_url, crate_name)?; self.get(url) } /// Retrieve the owners of a crate. pub fn crate_owners(&self, crate_name: &str) -> Result, Error> { - let url = super::async_client::build_crate_owners_url(&self.base_url, crate_name)?; + let url = super::util::build_crate_owners_url(&self.base_url, crate_name)?; let resp: Owners = self.get(url)?; Ok(resp.users) } @@ -138,8 +138,7 @@ impl SyncClient { crate_name: &str, page: u64, ) -> Result { - let url = - super::async_client::build_crate_reverse_deps_url(&self.base_url, crate_name, page)?; + let url = super::util::build_crate_reverse_deps_url(&self.base_url, crate_name, page)?; let page = self.get::(url)?; let mut deps = ReverseDependencies { @@ -185,8 +184,7 @@ impl SyncClient { /// Retrieve the authors for a crate version. pub fn crate_authors(&self, crate_name: &str, version: &str) -> Result { - let url = - super::async_client::build_crate_authors_url(&self.base_url, crate_name, version)?; + let url = super::util::build_crate_authors_url(&self.base_url, crate_name, version)?; let res: AuthorsResponse = self.get(url)?; Ok(Authors { names: res.meta.names, @@ -199,8 +197,7 @@ impl SyncClient { crate_name: &str, version: &str, ) -> Result, Error> { - let url = - super::async_client::build_crate_dependencies_url(&self.base_url, crate_name, version)?; + let url = super::util::build_crate_dependencies_url(&self.base_url, crate_name, version)?; let resp: Dependencies = self.get(url)?; Ok(resp.dependencies) } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..db2f706 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,75 @@ +use reqwest::Url; + +use super::Error; + +pub(crate) fn build_crate_url(base: &Url, crate_name: &str) -> Result { + 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::NotFoundError { + url: url.to_string(), + })) + } else { + Ok(url) + } +} + +fn build_crate_url_nested(base: &Url, crate_name: &str) -> Result { + let mut url = base.join("crates")?; + url.path_segments_mut().unwrap().push(crate_name).push("/"); + + // 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::NotFoundError { + url: url.to_string(), + })) + } else { + Ok(url) + } +} + +pub(crate) fn build_crate_downloads_url(base: &Url, crate_name: &str) -> Result { + build_crate_url_nested(base, crate_name)? + .join("downloads") + .map_err(Error::from) +} + +pub(crate) fn build_crate_owners_url(base: &Url, crate_name: &str) -> Result { + build_crate_url_nested(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 { + build_crate_url_nested(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 { + build_crate_url_nested(base, crate_name)? + .join(&format!("{version}/authors")) + .map_err(Error::from) +} + +pub(crate) fn build_crate_dependencies_url( + base: &Url, + crate_name: &str, + version: &str, +) -> Result { + build_crate_url_nested(base, crate_name)? + .join(&format!("{version}/dependencies")) + .map_err(Error::from) +}