mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-04-21 04:58:42 +00:00

* Refactor: Create new crate binstalk-git-repo-api Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix CI lint warnings Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix `just check`: Rm deleted features from `cargo-hack` check Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Extract new mod error Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Ret artifact url in `has_release_artifact` So that we can use it to download from private repositories. Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Move `test_graph_ql_error_type` to mod `error` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix running `cargo test` in `binstalk-git-repo-api`` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Remove unnecessary import in mod `error::test` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Rename mod `request`` to `release_artifacts` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Impl draft version of fetching repo info Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Move `HasReleaseArtifacts` failure variants into `GhApiError` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Use `GhRepo` in `GhRelease` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix testing Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Return `'static` future Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Make sure `'static` Future is returned To make it easier to create generic function Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add logging to unit testing Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix unit testing Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Extract new fn `GhApiClient::do_fetch` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Rm unused `percent_encode_http_url_path` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix `cargo test` run on CI `cargo test` run all tests in one process. As such, `set_global_default` would fail on the second call. Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Optimize `GhApiClient::do_fetch`: Avoid unnecessary restful API call Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Rm param `auth_token` for restful API fn which is always set to `None` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Impl new API `GhApiClient::get_repo_info` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix unit test for `GhApiClient::get_repo_info` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor testing: Parameter-ize testing Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Parallelise `test_get_repo_info` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Create parameter-ised `test_has_release_artifact` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Parallelize `test_has_release_artifact` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: `gh_api_client::test::create_client` shall not be `async` as there is no `.await` in it. Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Return `Url` in `GhApiClient::has_release_artifact` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Impl new API `GhApiClient::download_artifact` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Remove unused deps added to binstalk-git-repo-api Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix clippy lints Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add new API `GhApiClient::remote_client` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add `GhApiClient::has_gh_token` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add `GhRepo::try_extract_from_url` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Rename `ReleaseArtifactUrl` to `GhReleaseArtifactUrl` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add new fn `Download::with_data_verifier` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * feature: Support private repository Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix clippy lints Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add e2e-test/private-github-repo Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix clippy lints Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix `launch_baseline_find_tasks`: Retry on rate limit Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix test failure: Retry on rate limit Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Temporarily enable debug output for e2e-test-private-github-repo Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix `get_repo_info`: Retry on rate limit Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Improve `debug!` logging Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add more debug logging Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add more debugging Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add more debug logging Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Apply suggestions from code review * Fix compilation Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix cargo fmt Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add crate binstalk-git-repo-api to release-pr.yml * Update crates/binstalk-git-repo-api/Cargo.toml * Apply suggestions from code review * Update crates/binstalk/Cargo.toml --------- Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
203 lines
5.3 KiB
Rust
203 lines
5.3 KiB
Rust
use std::{error, fmt, io, time::Duration};
|
|
|
|
use binstalk_downloader::remote;
|
|
use compact_str::{CompactString, ToCompactString};
|
|
use serde::{de::Deserializer, Deserialize};
|
|
use thiserror::Error as ThisError;
|
|
|
|
#[derive(ThisError, Debug)]
|
|
#[error("Context: '{context}', err: '{err}'")]
|
|
pub struct GhApiContextError {
|
|
context: CompactString,
|
|
#[source]
|
|
err: GhApiError,
|
|
}
|
|
|
|
#[derive(ThisError, Debug)]
|
|
#[non_exhaustive]
|
|
pub enum GhApiError {
|
|
#[error("IO Error: {0}")]
|
|
Io(#[from] io::Error),
|
|
|
|
#[error("Remote Error: {0}")]
|
|
Remote(#[from] remote::Error),
|
|
|
|
#[error("Failed to parse url: {0}")]
|
|
InvalidUrl(#[from] url::ParseError),
|
|
|
|
/// A wrapped error providing the context the error is about.
|
|
#[error(transparent)]
|
|
Context(Box<GhApiContextError>),
|
|
|
|
#[error("Remote failed to process GraphQL query: {0}")]
|
|
GraphQLErrors(GhGraphQLErrors),
|
|
|
|
#[error("Hit rate-limit, retry after {retry_after:?}")]
|
|
RateLimit { retry_after: Option<Duration> },
|
|
|
|
#[error("Corresponding resource is not found")]
|
|
NotFound,
|
|
|
|
#[error("Does not have permission to access the API")]
|
|
Unauthorized,
|
|
}
|
|
|
|
impl GhApiError {
|
|
/// Attach context to [`GhApiError`]
|
|
pub fn context(self, context: impl fmt::Display) -> Self {
|
|
use GhApiError::*;
|
|
|
|
if matches!(self, RateLimit { .. } | NotFound | Unauthorized) {
|
|
self
|
|
} else {
|
|
Self::Context(Box::new(GhApiContextError {
|
|
context: context.to_compact_string(),
|
|
err: self,
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<GhGraphQLErrors> for GhApiError {
|
|
fn from(e: GhGraphQLErrors) -> Self {
|
|
if e.is_rate_limited() {
|
|
Self::RateLimit { retry_after: None }
|
|
} else if e.is_not_found_error() {
|
|
Self::NotFound
|
|
} else {
|
|
Self::GraphQLErrors(e)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct GhGraphQLErrors(Box<[GraphQLError]>);
|
|
|
|
impl GhGraphQLErrors {
|
|
fn is_rate_limited(&self) -> bool {
|
|
self.0
|
|
.iter()
|
|
.any(|error| matches!(error.error_type, GraphQLErrorType::RateLimited))
|
|
}
|
|
|
|
fn is_not_found_error(&self) -> bool {
|
|
self.0
|
|
.iter()
|
|
.any(|error| matches!(&error.error_type, GraphQLErrorType::Other(error_type) if *error_type == "NOT_FOUND"))
|
|
}
|
|
}
|
|
|
|
impl error::Error for GhGraphQLErrors {}
|
|
|
|
impl fmt::Display for GhGraphQLErrors {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
let last_error_index = self.0.len() - 1;
|
|
|
|
for (i, error) in self.0.iter().enumerate() {
|
|
write!(
|
|
f,
|
|
"type: '{error_type}', msg: '{msg}'",
|
|
error_type = error.error_type,
|
|
msg = error.message,
|
|
)?;
|
|
|
|
for location in error.locations.as_deref().into_iter().flatten() {
|
|
write!(
|
|
f,
|
|
", occured on query line {line} col {col}",
|
|
line = location.line,
|
|
col = location.column
|
|
)?;
|
|
}
|
|
|
|
for (k, v) in &error.others {
|
|
write!(f, ", {k}: {v}")?;
|
|
}
|
|
|
|
if i < last_error_index {
|
|
f.write_str("\n")?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct GraphQLError {
|
|
message: CompactString,
|
|
locations: Option<Box<[GraphQLLocation]>>,
|
|
|
|
#[serde(rename = "type")]
|
|
error_type: GraphQLErrorType,
|
|
|
|
#[serde(flatten, with = "tuple_vec_map")]
|
|
others: Vec<(CompactString, serde_json::Value)>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(super) enum GraphQLErrorType {
|
|
RateLimited,
|
|
Other(CompactString),
|
|
}
|
|
|
|
impl fmt::Display for GraphQLErrorType {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.write_str(match self {
|
|
GraphQLErrorType::RateLimited => "RATE_LIMITED",
|
|
GraphQLErrorType::Other(s) => s,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for GraphQLErrorType {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let s = CompactString::deserialize(deserializer)?;
|
|
Ok(match &*s {
|
|
"RATE_LIMITED" => GraphQLErrorType::RateLimited,
|
|
_ => GraphQLErrorType::Other(s),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct GraphQLLocation {
|
|
line: u64,
|
|
column: u64,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
use serde::de::value::{BorrowedStrDeserializer, Error};
|
|
|
|
macro_rules! assert_matches {
|
|
($expression:expr, $pattern:pat $(if $guard:expr)? $(,)?) => {
|
|
match $expression {
|
|
$pattern $(if $guard)? => true,
|
|
expr => {
|
|
panic!(
|
|
"assertion failed: `{expr:?}` does not match `{}`",
|
|
stringify!($pattern $(if $guard)?)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_graph_ql_error_type() {
|
|
let deserialize = |input: &str| {
|
|
GraphQLErrorType::deserialize(BorrowedStrDeserializer::<'_, Error>::new(input)).unwrap()
|
|
};
|
|
|
|
assert_matches!(deserialize("RATE_LIMITED"), GraphQLErrorType::RateLimited);
|
|
assert_matches!(
|
|
deserialize("rATE_LIMITED"),
|
|
GraphQLErrorType::Other(val) if val == CompactString::new("rATE_LIMITED")
|
|
);
|
|
}
|
|
}
|