diff --git a/crates/binstalk-git-repo-api/src/gh_api_client.rs b/crates/binstalk-git-repo-api/src/gh_api_client.rs index e265ae81..ee76d7c7 100644 --- a/crates/binstalk-git-repo-api/src/gh_api_client.rs +++ b/crates/binstalk-git-repo-api/src/gh_api_client.rs @@ -186,6 +186,21 @@ impl GhApiClient { .map_err(|err| err.context("Restful API")) } + pub async fn get_repo_info(&self, repo: &GhRepo) -> Result, GhApiError> { + match self + .do_fetch( + repo_info::fetch_repo_info_graphql_api, + repo_info::fetch_repo_info_restful_api, + repo, + ) + .await + { + Ok(repo_info) => Ok(repo_info), + Err(GhApiError::NotFound) => Ok(None), + Err(err) => Err(err), + } + } + /// Return `Ok(Some(api_artifact_url))` if exists. /// /// The returned future is guaranteed to be pointer size. @@ -350,17 +365,21 @@ mod test { let _ = set_global_default(subscriber); } - /// Mark this as an async fn so that you won't accidentally use it in - /// sync context. - async fn create_client() -> Vec { - let client = remote::Client::new( + fn create_remote_client() -> remote::Client { + remote::Client::new( concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")), None, NonZeroU16::new(10).unwrap(), 1.try_into().unwrap(), [], ) - .unwrap(); + .unwrap() + } + + /// Mark this as an async fn so that you won't accidentally use it in + /// sync context. + async fn create_client() -> Vec { + let client = create_remote_client(); let mut gh_clients = vec![GhApiClient::new(client.clone(), None)]; @@ -371,6 +390,42 @@ mod test { gh_clients } + async fn test_repo_info_public(repo: GhRepo, repo_info: Option) { + init_logger(); + + for client in create_client().await { + eprintln!("In client {client:?}"); + + let res = client.get_repo_info(&repo).await; + + if matches!(res, Err(GhApiError::RateLimit { .. })) { + continue; + } + + assert_eq!(res.unwrap(), repo_info); + } + } + + async fn test_repo_info_private(repo: GhRepo) { + init_logger(); + + let Ok(token) = env::var("GITHUB_TOKEN") else { + return; + }; + + let client = GhApiClient::new(create_remote_client(), Some(token.into())); + + eprintln!("In client {client:?}"); + + let res = client.get_repo_info(&repo).await; + + if matches!(res, Err(GhApiError::RateLimit { .. })) { + return; + } + + assert_eq!(res.unwrap(), Some(RepoInfo::new(repo, true))); + } + async fn test_specific_release(release: &GhRelease, artifacts: &[&str]) { init_logger(); @@ -407,6 +462,33 @@ mod test { } } + #[tokio::test] + async fn test_gh_api_client_cargo_binstall() { + let repo = GhRepo { + owner: "cargo-bins".to_compact_string(), + repo: "cargo-binstall".to_compact_string(), + }; + test_repo_info_public(repo.clone(), Some(RepoInfo::new(repo, false))).await + } + + #[tokio::test] + async fn test_gh_api_client_non_existent_repo() { + let repo = GhRepo { + owner: "cargo-bins".to_compact_string(), + repo: "ttt".to_compact_string(), + }; + test_repo_info_public(repo.clone(), None).await + } + + #[tokio::test] + async fn test_gh_api_client_private_repo() { + let repo = GhRepo { + owner: "cargo-bins".to_compact_string(), + repo: "private-repo-for-testing".to_compact_string(), + }; + test_repo_info_private(repo.clone()).await + } + #[tokio::test] async fn test_gh_api_client_cargo_binstall_v0_20_1() { test_specific_release( diff --git a/crates/binstalk-git-repo-api/src/gh_api_client/repo_info.rs b/crates/binstalk-git-repo-api/src/gh_api_client/repo_info.rs index 4aa6bbe7..04e4c9f8 100644 --- a/crates/binstalk-git-repo-api/src/gh_api_client/repo_info.rs +++ b/crates/binstalk-git-repo-api/src/gh_api_client/repo_info.rs @@ -1,3 +1,5 @@ +use std::future::Future; + use compact_str::CompactString; use serde::Deserialize; @@ -6,12 +8,12 @@ use super::{ remote, GhApiError, GhRepo, }; -#[derive(Debug, Deserialize)] +#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)] struct Owner { login: CompactString, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)] pub struct RepoInfo { owner: Owner, name: CompactString, @@ -19,6 +21,14 @@ pub struct RepoInfo { } impl RepoInfo { + #[cfg(test)] + pub(crate) fn new(GhRepo { owner, repo }: GhRepo, private: bool) -> Self { + Self { + owner: Owner { login: owner }, + name: repo, + private, + } + } pub fn repo(&self) -> GhRepo { GhRepo { owner: self.owner.login.clone(), @@ -31,11 +41,11 @@ impl RepoInfo { } } -pub(super) async fn fetch_repo_info_restful_api( +pub(super) fn fetch_repo_info_restful_api( client: &remote::Client, GhRepo { owner, repo }: &GhRepo, -) -> Result { - issue_restful_api(client, &["repos", owner, repo]).await +) -> impl Future, GhApiError>> + Send + Sync + 'static { + issue_restful_api(client, &["repos", owner, repo]) } #[derive(Deserialize)] @@ -43,11 +53,11 @@ struct GraphQLData { repository: Option, } -pub(super) async fn fetch_repo_info_graphql_api( +pub(super) fn fetch_repo_info_graphql_api( client: &remote::Client, GhRepo { owner, repo }: &GhRepo, auth_token: &str, -) -> Result { +) -> impl Future, GhApiError>> + Send + Sync + 'static { let query = format!( r#" query {{ @@ -61,5 +71,10 @@ query {{ }}"# ); - issue_graphql_query(client, query, auth_token).await + let future = issue_graphql_query(client, query, auth_token); + + async move { + let data: GraphQLData = future.await?; + Ok(data.repository) + } }