mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-04-20 20:48:43 +00:00
GhApiClient
: Fallback to unauthorized mode if auth token is invalid (#1121)
Fixed #850 Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
parent
351b9d074a
commit
2acba14b41
2 changed files with 61 additions and 35 deletions
|
@ -1,7 +1,10 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
sync::{Arc, Mutex, RwLock},
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering::Relaxed},
|
||||||
|
Arc, Mutex, RwLock,
|
||||||
|
},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -87,9 +90,11 @@ where
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Inner {
|
struct Inner {
|
||||||
client: remote::Client,
|
client: remote::Client,
|
||||||
auth_token: Option<CompactString>,
|
|
||||||
release_artifacts: Map<GhRelease, OnceCell<Option<request::Artifacts>>>,
|
release_artifacts: Map<GhRelease, OnceCell<Option<request::Artifacts>>>,
|
||||||
retry_after: Mutex<Option<Instant>>,
|
retry_after: Mutex<Option<Instant>>,
|
||||||
|
|
||||||
|
auth_token: Option<CompactString>,
|
||||||
|
is_auth_token_valid: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Github API client for querying whether a release artifact exitsts.
|
/// Github API client for querying whether a release artifact exitsts.
|
||||||
|
@ -116,11 +121,47 @@ impl GhApiClient {
|
||||||
|
|
||||||
Self(Arc::new(Inner {
|
Self(Arc::new(Inner {
|
||||||
client,
|
client,
|
||||||
auth_token,
|
|
||||||
release_artifacts: Default::default(),
|
release_artifacts: Default::default(),
|
||||||
retry_after: Default::default(),
|
retry_after: Default::default(),
|
||||||
|
|
||||||
|
auth_token,
|
||||||
|
is_auth_token_valid: AtomicBool::new(true),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FetchReleaseArtifactError {
|
||||||
|
Error(GhApiError),
|
||||||
|
RateLimit { retry_after: Instant },
|
||||||
|
Unauthorized,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GhApiClient {
|
||||||
|
async fn do_fetch_release_artifacts(
|
||||||
|
&self,
|
||||||
|
release: &GhRelease,
|
||||||
|
auth_token: Option<&str>,
|
||||||
|
) -> Result<Option<request::Artifacts>, FetchReleaseArtifactError> {
|
||||||
|
use request::FetchReleaseRet::*;
|
||||||
|
use FetchReleaseArtifactError as Error;
|
||||||
|
|
||||||
|
match request::fetch_release_artifacts(&self.0.client, release, auth_token).await {
|
||||||
|
Ok(ReleaseNotFound) => Ok(None),
|
||||||
|
Ok(Artifacts(artifacts)) => Ok(Some(artifacts)),
|
||||||
|
Ok(ReachedRateLimit { retry_after }) => {
|
||||||
|
let retry_after = retry_after.unwrap_or(DEFAULT_RETRY_DURATION);
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
let retry_after = now
|
||||||
|
.checked_add(retry_after)
|
||||||
|
.unwrap_or_else(|| now + DEFAULT_RETRY_DURATION);
|
||||||
|
|
||||||
|
Err(Error::RateLimit { retry_after })
|
||||||
|
}
|
||||||
|
Ok(Unauthorized) => Err(Error::Unauthorized),
|
||||||
|
Err(err) => Err(Error::Error(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The returned future is guaranteed to be pointer size.
|
/// The returned future is guaranteed to be pointer size.
|
||||||
pub async fn has_release_artifact(
|
pub async fn has_release_artifact(
|
||||||
|
@ -130,24 +171,18 @@ impl GhApiClient {
|
||||||
artifact_name,
|
artifact_name,
|
||||||
}: GhReleaseArtifact,
|
}: GhReleaseArtifact,
|
||||||
) -> Result<HasReleaseArtifact, GhApiError> {
|
) -> Result<HasReleaseArtifact, GhApiError> {
|
||||||
enum Failure {
|
use FetchReleaseArtifactError as Error;
|
||||||
Error(GhApiError),
|
|
||||||
RateLimit { retry_after: Instant },
|
|
||||||
Unauthorized,
|
|
||||||
}
|
|
||||||
|
|
||||||
let once_cell = self.0.release_artifacts.get(release.clone());
|
let once_cell = self.0.release_artifacts.get(release.clone());
|
||||||
let res = once_cell
|
let res = once_cell
|
||||||
.get_or_try_init(|| {
|
.get_or_try_init(|| {
|
||||||
Box::pin(async {
|
Box::pin(async {
|
||||||
use request::FetchReleaseRet::*;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut guard = self.0.retry_after.lock().unwrap();
|
let mut guard = self.0.retry_after.lock().unwrap();
|
||||||
|
|
||||||
if let Some(retry_after) = *guard {
|
if let Some(retry_after) = *guard {
|
||||||
if retry_after.elapsed().is_zero() {
|
if retry_after.elapsed().is_zero() {
|
||||||
return Err(Failure::RateLimit { retry_after });
|
return Err(Error::RateLimit { retry_after });
|
||||||
} else {
|
} else {
|
||||||
// Instant retry_after is already reached.
|
// Instant retry_after is already reached.
|
||||||
*guard = None;
|
*guard = None;
|
||||||
|
@ -155,28 +190,19 @@ impl GhApiClient {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
match request::fetch_release_artifacts(
|
if self.0.is_auth_token_valid.load(Relaxed) {
|
||||||
&self.0.client,
|
match self
|
||||||
release,
|
.do_fetch_release_artifacts(&release, self.0.auth_token.as_deref())
|
||||||
self.0.auth_token.as_deref(),
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(ReleaseNotFound) => Ok::<_, Failure>(None),
|
Err(Error::Unauthorized) => {
|
||||||
Ok(Artifacts(artifacts)) => Ok(Some(artifacts)),
|
self.0.is_auth_token_valid.store(false, Relaxed);
|
||||||
Ok(ReachedRateLimit { retry_after }) => {
|
|
||||||
let retry_after = retry_after.unwrap_or(DEFAULT_RETRY_DURATION);
|
|
||||||
|
|
||||||
let now = Instant::now();
|
|
||||||
let retry_after = now
|
|
||||||
.checked_add(retry_after)
|
|
||||||
.unwrap_or_else(|| now + DEFAULT_RETRY_DURATION);
|
|
||||||
|
|
||||||
Err(Failure::RateLimit { retry_after })
|
|
||||||
}
|
}
|
||||||
Ok(Unauthorized) => Err(Failure::Unauthorized),
|
res => return res,
|
||||||
Err(err) => Err(Failure::Error(err)),
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.do_fetch_release_artifacts(&release, None).await
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
@ -191,13 +217,13 @@ impl GhApiClient {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Ok(None) => Ok(HasReleaseArtifact::NoSuchRelease),
|
Ok(None) => Ok(HasReleaseArtifact::NoSuchRelease),
|
||||||
Err(Failure::Unauthorized) => Ok(HasReleaseArtifact::Unauthorized),
|
Err(Error::Unauthorized) => Ok(HasReleaseArtifact::Unauthorized),
|
||||||
Err(Failure::RateLimit { retry_after }) => {
|
Err(Error::RateLimit { retry_after }) => {
|
||||||
*self.0.retry_after.lock().unwrap() = Some(retry_after);
|
*self.0.retry_after.lock().unwrap() = Some(retry_after);
|
||||||
|
|
||||||
Ok(HasReleaseArtifact::RateLimit { retry_after })
|
Ok(HasReleaseArtifact::RateLimit { retry_after })
|
||||||
}
|
}
|
||||||
Err(Failure::Error(err)) => Err(err),
|
Err(Error::Error(err)) => Err(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ pub(super) enum FetchReleaseRet {
|
||||||
/// Returns 404 if not found
|
/// Returns 404 if not found
|
||||||
pub(super) async fn fetch_release_artifacts(
|
pub(super) async fn fetch_release_artifacts(
|
||||||
client: &remote::Client,
|
client: &remote::Client,
|
||||||
GhRelease { owner, repo, tag }: GhRelease,
|
GhRelease { owner, repo, tag }: &GhRelease,
|
||||||
auth_token: Option<&str>,
|
auth_token: Option<&str>,
|
||||||
) -> Result<FetchReleaseRet, GhApiError> {
|
) -> Result<FetchReleaseRet, GhApiError> {
|
||||||
let mut request_builder = client
|
let mut request_builder = client
|
||||||
|
|
Loading…
Add table
Reference in a new issue