Skip to content

Commit

Permalink
Merge pull request #693 from SierraSoftworks/feat/github-retries
Browse files Browse the repository at this point in the history
feat: Add support for retrying failed requests to GitHub
  • Loading branch information
notheotherben authored Nov 18, 2022
2 parents 9b468ee + 8695ae7 commit 2dac5db
Showing 1 changed file with 62 additions and 26 deletions.
88 changes: 62 additions & 26 deletions src/online/service/github.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::time::Duration;

use super::*;
use crate::errors;
use reqwest::{Method, Request, StatusCode};
use reqwest::{Method, Request, StatusCode, Url};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -113,7 +115,7 @@ impl GitHubService {
}
}

async fn make_request<B: Into<reqwest::Body>, T: DeserializeOwned>(
async fn make_request<B: Into<reqwest::Body> + Clone, T: DeserializeOwned>(
&self,
core: &Core,
service: &Service,
Expand All @@ -122,7 +124,7 @@ impl GitHubService {
body: B,
acceptable: Vec<StatusCode>,
) -> Result<Result<T, GitHubErrorResponse>, Error> {
let url = uri.parse().map_err(|e| {
let url: Url = uri.parse().map_err(|e| {
errors::system_with_internal(
&format!("Unable to parse GitHub API URL '{}'.", uri),
"Please report this error to us by opening an issue on GitHub.",
Expand All @@ -132,29 +134,63 @@ impl GitHubService {

let token = core.keychain().get_token(&service.name)?;

let mut req = Request::new(method, url);

*req.body_mut() = Some(body.into());

let headers = req.headers_mut();

headers.append("User-Agent", version!("Git-Tool/v").parse()?);
headers.append("Accept", "application/vnd.github.v3+json".parse()?);
headers.append("Authorization", format!("token {}", token).parse()?);

let resp = core.http_client().request(req).await?;

match resp.status() {
status if acceptable.contains(&status) => {
let result = resp.json().await?;

Ok(Ok(result))
}
status => {
let mut result: GitHubErrorResponse = resp.json().await?;
result.http_status_code = status;

Ok(Err(result))
let mut remaining_attempts = 3;
let retryable = vec![
StatusCode::TOO_MANY_REQUESTS,
StatusCode::INTERNAL_SERVER_ERROR,
StatusCode::BAD_GATEWAY,
StatusCode::SERVICE_UNAVAILABLE,
];

loop {
remaining_attempts -= 1;

let mut req = Request::new(method.clone(), url.clone());

*req.body_mut() = Some(body.clone().into());

let headers = req.headers_mut();

headers.append("User-Agent", version!("Git-Tool/v").parse()?);
headers.append("Accept", "application/vnd.github.v3+json".parse()?);
headers.append("Authorization", format!("token {}", token).parse()?);

match core.http_client().request(req).await {
Ok(resp) if acceptable.contains(&resp.status()) => {
let result = resp.json().await?;

return Ok(Ok(result));
}
Ok(resp) if remaining_attempts > 0 && retryable.contains(&resp.status()) => {
tracing::warn!(
"GitHub API request failed with status code {}. Retrying...",
resp.status()
);

tokio::time::sleep(Duration::from_secs(1)).await;

continue;
}
Ok(resp) => {
let status = resp.status();
let mut result: GitHubErrorResponse = resp.json().await?;
result.http_status_code = status;

return Ok(Err(result));
}
Err(error) if remaining_attempts > 0 => {
tracing::warn!(
"GitHub API request failed with error {}. Retrying...",
error
);

tokio::time::sleep(Duration::from_secs(1)).await;

continue;
}
Err(error) => {
return Err(error);
}
}
}
}
Expand Down

0 comments on commit 2dac5db

Please sign in to comment.