Skip to content

Commit

Permalink
Refactor: Extract new mod error
Browse files Browse the repository at this point in the history
Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
  • Loading branch information
NobodyXu committed May 6, 2024
1 parent 8bda7ea commit 74bfc87
Show file tree
Hide file tree
Showing 2 changed files with 8 additions and 138 deletions.
4 changes: 3 additions & 1 deletion crates/binstalk-git-repo-api/src/gh_api_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ use percent_encoding::{
use tokio::sync::OnceCell;

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
const DEFAULT_RETRY_DURATION: Duration = Duration::from_secs(10 * 60);
Expand Down
142 changes: 5 additions & 137 deletions crates/binstalk-git-repo-api/src/gh_api_client/request.rs
Original file line number Diff line number Diff line change
@@ -1,59 +1,19 @@
use std::{
borrow::Borrow,
collections::HashSet,
error, fmt,
fmt,
hash::{Hash, Hasher},
io,
sync::OnceLock,
time::Duration,
};

use binstalk_downloader::remote::{header::HeaderMap, StatusCode, Url};
use compact_str::{CompactString, ToCompactString};
use serde::{de::Deserializer, Deserialize, Serialize};
use compact_str::CompactString;
use serde::{Deserialize, Serialize};
use serde_json::to_string as to_json_string;
use thiserror::Error as ThisError;
use tracing::debug;

use super::{percent_encode_http_url_path, remote, 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,
}))
}
}
use super::{percent_encode_http_url_path, remote, GhApiError, GhGraphQLErrors, GhRelease};

// Only include fields we do care about

Expand Down Expand Up @@ -169,99 +129,6 @@ enum GraphQLResponse {
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)]
struct GraphQLData {
repository: Option<GraphQLRepo>,
Expand Down Expand Up @@ -415,6 +282,7 @@ pub(super) async fn fetch_release_artifacts(
#[cfg(test)]
mod test {
use super::*;
use crate::gh_api_client::error::GraphQLErrorType;
use serde::de::value::{BorrowedStrDeserializer, Error};

macro_rules! assert_matches {
Expand Down

0 comments on commit 74bfc87

Please sign in to comment.