Skip to content

Commit 1abd8be

Browse files
committed
feat(fetcher): add retry logic with exponential backoff and update template repo URL
- Updated GITHUB_RAW_BASE to point to Byte-Barn/gitcraft instead of rafaeljohn9/gitcraft - Added retry logic with exponential backoff (max 3 attempts) to fetch_content and fetch_json - Improved error messages to include HTTP status codes and reasons - Adjusted integration test assertions to match new error message format ("Failed" vs "Request failed")
1 parent 242efd8 commit 1abd8be

4 files changed

Lines changed: 60 additions & 45 deletions

File tree

src/commands/issue/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ pub mod list;
77
pub mod preview;
88

99
// Global constants - these can stay in the main module file
10-
const GITHUB_RAW_BASE: &str =
11-
"https://raw.githubusercontent.com/rafaeljohn9/gitcraft/main/templates";
10+
const GITHUB_RAW_BASE: &str = "https://raw.githubusercontent.com/Byte-Barn/gitcraft/main/templates";
1211

1312
#[derive(Subcommand)]
1413
pub enum Command {

src/commands/pr/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ pub mod list;
77
pub mod preview;
88

99
// Global constants - these can stay in the main module file
10-
const GITHUB_RAW_BASE: &str =
11-
"https://raw.githubusercontent.com/rafaeljohn9/gitcraft/main/templates";
10+
const GITHUB_RAW_BASE: &str = "https://raw.githubusercontent.com/Byte-Barn/gitcraft/main/templates";
1211

1312
#[derive(Subcommand)]
1413
pub enum Command {

src/utils/remote.rs

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,45 +18,64 @@ impl Fetcher {
1818
}
1919
}
2020

21-
/// Fetch raw content from a URL
21+
/// Fetch raw content from a URL with retry logic
2222
pub fn fetch_content(&self, url: &str) -> anyhow::Result<String> {
23-
let response = self
24-
.client
25-
.get(url)
26-
.send()
27-
.map_err(|e| anyhow!("Failed to fetch from {}: {}", url, e))?;
28-
29-
if !response.status().is_success() {
30-
return Err(anyhow!(
31-
"Request failed with status {}: {}",
32-
response.status(),
33-
url
34-
));
35-
}
36-
37-
response
38-
.text()
39-
.map_err(|e| anyhow!("Failed to read response: {}", e))
23+
self.retry(|| self.client.get(url).send())
24+
.and_then(|response| {
25+
if !response.status().is_success() {
26+
return Err(anyhow!(
27+
"Failed to fetch from {}: HTTP {} ({})",
28+
url,
29+
response.status().as_u16(),
30+
response.status().canonical_reason().unwrap_or("Unknown")
31+
));
32+
}
33+
Ok(response)
34+
})
35+
.and_then(|response| {
36+
response
37+
.text()
38+
.map_err(|e| anyhow!("Failed to read response: {}", e))
39+
})
4040
}
4141

42-
/// Fetch and parse JSON from a URL
42+
/// Fetch and parse JSON from a URL with retry logic
4343
pub fn fetch_json(&self, url: &str) -> anyhow::Result<serde_json::Value> {
44-
let response = self
45-
.client
46-
.get(url)
47-
.send()
48-
.map_err(|e| anyhow!("Failed to fetch JSON from {}: {}", url, e))?;
44+
self.retry(|| self.client.get(url).send())
45+
.and_then(|response| {
46+
if !response.status().is_success() {
47+
return Err(anyhow!(
48+
"JSON request failed with status {}: {}",
49+
response.status(),
50+
url
51+
));
52+
}
53+
Ok(response)
54+
})
55+
.and_then(|response| {
56+
response
57+
.json()
58+
.map_err(|e| anyhow!("Failed to parse JSON: {}", e))
59+
})
60+
}
4961

50-
if !response.status().is_success() {
51-
return Err(anyhow!(
52-
"JSON request failed with status {}: {}",
53-
response.status(),
54-
url
55-
));
62+
/// Retry logic with exponential backoff (max 3 attempts)
63+
fn retry<F>(&self, mut f: F) -> anyhow::Result<reqwest::blocking::Response>
64+
where
65+
F: FnMut() -> Result<reqwest::blocking::Response, reqwest::Error>,
66+
{
67+
let mut attempts = 0;
68+
loop {
69+
match f() {
70+
Ok(response) => return Ok(response),
71+
Err(e) => {
72+
attempts += 1;
73+
if attempts >= 3 {
74+
return Err(anyhow!("Request failed after 3 attempts: {}", e));
75+
}
76+
std::thread::sleep(Duration::from_millis(100 * (2_u64.pow(attempts - 1))));
77+
}
78+
}
5679
}
57-
58-
response
59-
.json()
60-
.map_err(|e| anyhow!("Failed to parse JSON: {}", e))
6180
}
6281
}

tests/integration/issue_tests.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,7 @@ fn test_issue_add_invalid_type() {
130130
cmd.args(&["add", "issue", "invalid-template"])
131131
.assert()
132132
.failure()
133-
.stderr(
134-
predicate::str::contains("Request failed").or(predicate::str::contains("not found")),
135-
);
133+
.stderr(predicate::str::contains("Failed").or(predicate::str::contains("not found")));
136134
}
137135

138136
#[test]
@@ -301,9 +299,7 @@ fn test_issue_preview_invalid_id() {
301299
cmd.args(&["preview", "issue", "not-a-template"])
302300
.assert()
303301
.failure()
304-
.stderr(
305-
predicate::str::contains("Request failed").or(predicate::str::contains("not found")),
306-
);
302+
.stderr(predicate::str::contains("Failed").or(predicate::str::contains("not found")));
307303
}
308304

309305
// -------- HELP COMMAND TEST --------
@@ -318,7 +314,9 @@ fn test_issue_help_command() {
318314
.assert()
319315
.success()
320316
.stdout(predicate::str::contains("Add an issue template"))
321-
.stdout(predicate::str::contains("Usage: gitcraft add issue-template"))
317+
.stdout(predicate::str::contains(
318+
"Usage: gitcraft add issue-template",
319+
))
322320
.stdout(predicate::str::contains("--dir"))
323321
.stdout(predicate::str::contains("--force"))
324322
.stdout(predicate::str::contains("-o, --output"));

0 commit comments

Comments
 (0)