mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-06-17 16:16:37 +00:00
Refactor: Extract new mod error
Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
parent
8bda7eadf5
commit
d2914cca42
3 changed files with 145 additions and 138 deletions
|
@ -16,7 +16,9 @@ use percent_encoding::{
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
|
|
||||||
mod request;
|
mod request;
|
||||||
pub use request::{GhApiContextError, GhApiError, GhGraphQLErrors};
|
|
||||||
|
mod error;
|
||||||
|
pub use error::{GhApiContextError, GhApiError, GhGraphQLErrors};
|
||||||
|
|
||||||
/// default retry duration if x-ratelimit-reset is not found in response header
|
/// default retry duration if x-ratelimit-reset is not found in response header
|
||||||
const DEFAULT_RETRY_DURATION: Duration = Duration::from_secs(10 * 60);
|
const DEFAULT_RETRY_DURATION: Duration = Duration::from_secs(10 * 60);
|
||||||
|
|
137
crates/binstalk-git-repo-api/src/gh_api_client/error.rs
Normal file
137
crates/binstalk-git-repo-api/src/gh_api_client/error.rs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
use std::{error, fmt, io};
|
||||||
|
|
||||||
|
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(#[from] GhGraphQLErrors),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GhApiError {
|
||||||
|
/// Attach context to [`GhApiError`]
|
||||||
|
pub fn context(self, context: impl fmt::Display) -> Self {
|
||||||
|
Self::Context(Box::new(GhApiContextError {
|
||||||
|
context: context.to_compact_string(),
|
||||||
|
err: self,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct GhGraphQLErrors(Box<[GraphQLError]>);
|
||||||
|
|
||||||
|
impl GhGraphQLErrors {
|
||||||
|
pub(super) fn is_rate_limited(&self) -> bool {
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.any(|error| matches!(error.error_type, GraphQLErrorType::RateLimited))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
|
@ -1,59 +1,19 @@
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Borrow,
|
borrow::Borrow,
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
error, fmt,
|
fmt,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
io,
|
|
||||||
sync::OnceLock,
|
sync::OnceLock,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use binstalk_downloader::remote::{header::HeaderMap, StatusCode, Url};
|
use binstalk_downloader::remote::{header::HeaderMap, StatusCode, Url};
|
||||||
use compact_str::{CompactString, ToCompactString};
|
use compact_str::CompactString;
|
||||||
use serde::{de::Deserializer, Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::to_string as to_json_string;
|
use serde_json::to_string as to_json_string;
|
||||||
use thiserror::Error as ThisError;
|
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use super::{percent_encode_http_url_path, remote, GhRelease};
|
use super::{percent_encode_http_url_path, remote, GhApiError, GhGraphQLErrors, GhRelease};
|
||||||
|
|
||||||
#[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(#[from] GhGraphQLErrors),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GhApiError {
|
|
||||||
/// Attach context to [`GhApiError`]
|
|
||||||
pub fn context(self, context: impl fmt::Display) -> Self {
|
|
||||||
Self::Context(Box::new(GhApiContextError {
|
|
||||||
context: context.to_compact_string(),
|
|
||||||
err: self,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only include fields we do care about
|
// Only include fields we do care about
|
||||||
|
|
||||||
|
@ -169,99 +129,6 @@ enum GraphQLResponse {
|
||||||
Errors(GhGraphQLErrors),
|
Errors(GhGraphQLErrors),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)]
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct GraphQLData {
|
struct GraphQLData {
|
||||||
repository: Option<GraphQLRepo>,
|
repository: Option<GraphQLRepo>,
|
||||||
|
@ -415,6 +282,7 @@ pub(super) async fn fetch_release_artifacts(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::gh_api_client::error::GraphQLErrorType;
|
||||||
use serde::de::value::{BorrowedStrDeserializer, Error};
|
use serde::de::value::{BorrowedStrDeserializer, Error};
|
||||||
|
|
||||||
macro_rules! assert_matches {
|
macro_rules! assert_matches {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue