minecraft_java_rs_core/net/
http.rs1use std::time::Duration;
2
3use serde::de::DeserializeOwned;
4
5const MAX_RETRIES: u32 = 3;
6const INITIAL_BACKOFF_MS: u64 = 1_000;
7
8fn is_retryable_status(status: reqwest::StatusCode) -> bool {
11 status.is_server_error() || status == reqwest::StatusCode::TOO_MANY_REQUESTS
12}
13
14pub async fn fetch_json<T: DeserializeOwned>(
22 client: &reqwest::Client,
23 url: &str,
24) -> Result<T, String> {
25 let mut last_err = String::new();
26 let mut backoff = INITIAL_BACKOFF_MS;
27
28 for attempt in 0..=MAX_RETRIES {
29 if attempt > 0 {
30 tokio::time::sleep(Duration::from_millis(backoff)).await;
31 backoff = (backoff * 2).min(16_000);
32 }
33
34 let resp = match client.get(url).send().await {
35 Ok(r) => r,
36 Err(e) => {
37 last_err = format!("GET {url}: {e}");
38 continue;
39 }
40 };
41
42 let status = resp.status();
43 if is_retryable_status(status) {
44 let reason = status.canonical_reason().unwrap_or("unknown");
45 last_err = format!("GET {url}: HTTP {status} {reason}");
46 continue;
47 }
48 if !status.is_success() {
49 let reason = status.canonical_reason().unwrap_or("unknown");
50 return Err(format!("GET {url}: HTTP {status} {reason}"));
51 }
52
53 let text = match resp.text().await {
54 Ok(t) => t,
55 Err(e) => {
56 last_err = format!("GET {url}: failed to read response body: {e}");
57 continue;
58 }
59 };
60
61 return serde_json::from_str(&text).map_err(|e| {
62 let preview: String = text.chars().take(300).collect();
63 format!("GET {url}: failed to parse JSON: {e}\nBody: {preview}")
64 });
65 }
66
67 Err(format!("{last_err} (failed after {MAX_RETRIES} retries)"))
68}
69
70pub async fn fetch_text(
75 client: &reqwest::Client,
76 url: &str,
77) -> Result<String, String> {
78 let mut last_err = String::new();
79 let mut backoff = INITIAL_BACKOFF_MS;
80
81 for attempt in 0..=MAX_RETRIES {
82 if attempt > 0 {
83 tokio::time::sleep(Duration::from_millis(backoff)).await;
84 backoff = (backoff * 2).min(16_000);
85 }
86
87 let resp = match client.get(url).send().await {
88 Ok(r) => r,
89 Err(e) => {
90 last_err = format!("GET {url}: {e}");
91 continue;
92 }
93 };
94
95 let status = resp.status();
96 if is_retryable_status(status) {
97 let reason = status.canonical_reason().unwrap_or("unknown");
98 last_err = format!("GET {url}: HTTP {status} {reason}");
99 continue;
100 }
101 if !status.is_success() {
102 let reason = status.canonical_reason().unwrap_or("unknown");
103 return Err(format!("GET {url}: HTTP {status} {reason}"));
104 }
105
106 return resp
107 .text()
108 .await
109 .map_err(|e| format!("GET {url}: failed to read response body: {e}"));
110 }
111
112 Err(format!("{last_err} (failed after {MAX_RETRIES} retries)"))
113}