1use std::sync::atomic::{AtomicU32, Ordering};
2use std::sync::Arc;
3use std::time::{Duration, SystemTime, UNIX_EPOCH};
4
5use reqwest::{Client, RequestBuilder, Response};
6use serde::{de::DeserializeOwned, Serialize};
7use tokio::sync::RwLock;
8use tokio::time::Instant;
9
10use crate::{
11 config::HighLevelConfig,
12 error::{Error, Result},
13};
14
15const DEFAULT_RATE: u32 = 95;
16const DEFAULT_WINDOW_SECS: u64 = 10;
17const MAX_RETRIES: u32 = 3;
18const CIRCUIT_BREAK_THRESHOLD: u32 = 3;
19const CIRCUIT_BREAK_RESET_SECS: u64 = 30;
20
21struct TokenBucket {
22 capacity: u32,
23 tokens: f64,
24 refill_per_sec: f64,
25 last_refill: Instant,
26}
27
28impl TokenBucket {
29 fn new(capacity: u32, window_secs: u64) -> Self {
30 Self {
31 capacity,
32 tokens: capacity as f64,
33 refill_per_sec: capacity as f64 / window_secs as f64,
34 last_refill: Instant::now(),
35 }
36 }
37
38 fn try_acquire(&mut self) -> bool {
39 self.refill();
40 if self.tokens >= 1.0 {
41 self.tokens -= 1.0;
42 true
43 } else {
44 false
45 }
46 }
47
48 fn refill(&mut self) {
49 let elapsed = self.last_refill.elapsed().as_secs_f64();
50 self.tokens = (self.tokens + elapsed * self.refill_per_sec).min(self.capacity as f64);
51 self.last_refill = Instant::now();
52 }
53}
54
55struct CircuitBreaker {
56 consecutive_429s: AtomicU32,
57 open_until: RwLock<Option<Instant>>,
58}
59
60impl CircuitBreaker {
61 fn new() -> Self {
62 Self {
63 consecutive_429s: AtomicU32::new(0),
64 open_until: RwLock::new(None),
65 }
66 }
67
68 async fn is_open(&self) -> bool {
69 if let Some(until) = *self.open_until.read().await {
70 if Instant::now() < until {
71 return true;
72 }
73 self.open_until.write().await.take();
74 self.consecutive_429s.store(0, Ordering::SeqCst);
75 }
76 false
77 }
78
79 async fn record_429(&self) {
80 let count = self.consecutive_429s.fetch_add(1, Ordering::SeqCst) + 1;
81 if count >= CIRCUIT_BREAK_THRESHOLD {
82 *self.open_until.write().await =
83 Some(Instant::now() + Duration::from_secs(CIRCUIT_BREAK_RESET_SECS));
84 }
85 }
86
87 async fn record_success(&self) {
88 self.consecutive_429s.store(0, Ordering::SeqCst);
89 self.open_until.write().await.take();
90 }
91}
92
93pub struct HttpClient {
94 inner: Client,
95 pub base_url: String,
96 pub api_version: String,
97 token: RwLock<Option<String>>,
98 bucket: RwLock<TokenBucket>,
99 breaker: CircuitBreaker,
100}
101
102impl HttpClient {
103 pub fn new(config: &HighLevelConfig) -> Self {
104 Self {
105 inner: Client::new(),
106 base_url: config.base_url.clone(),
107 api_version: config.api_version.clone(),
108 token: RwLock::new(None),
109 bucket: RwLock::new(TokenBucket::new(DEFAULT_RATE, DEFAULT_WINDOW_SECS)),
110 breaker: CircuitBreaker::new(),
111 }
112 }
113
114 pub async fn set_token(&self, token: String) {
115 *self.token.write().await = Some(token);
116 }
117
118 pub async fn get_token(&self) -> Option<String> {
119 self.token.read().await.clone()
120 }
121
122 fn url(&self, path: &str) -> String {
123 format!("{}{}", self.base_url, path)
124 }
125
126 async fn acquire_token(&self) -> Result<()> {
127 if self.breaker.is_open().await {
128 return Err(Error::RateLimited {
129 retry_after: Some(CIRCUIT_BREAK_RESET_SECS),
130 circuit_breaker: true,
131 });
132 }
133 loop {
134 if self.bucket.write().await.try_acquire() {
135 return Ok(());
136 }
137 tokio::time::sleep(Duration::from_millis(50)).await;
138 }
139 }
140
141 async fn execute_with_retry<F>(&self, build: F) -> Result<Response>
145 where
146 F: Fn(&Client) -> RequestBuilder,
147 {
148 self.acquire_token().await?;
149
150 let token = self.token.read().await.clone();
151 let mut builder = build(&self.inner);
152 if let Some(ref t) = token {
153 builder = builder.bearer_auth(t);
154 }
155 let req = builder.build()?;
156 let mut response = self.inner.execute(req).await?;
157
158 let mut retries = 0u32;
159 while response.status() == 429 && retries < MAX_RETRIES {
160 self.breaker.record_429().await;
161
162 let delay = exponential_backoff(retries) + jitter();
163 tokio::time::sleep(delay).await;
164
165 let token = self.token.read().await.clone();
166 let mut builder = build(&self.inner);
167 if let Some(ref t) = token {
168 builder = builder.bearer_auth(t);
169 }
170 let req = builder.build()?;
171 response = self.inner.execute(req).await?;
172 retries += 1;
173 }
174
175 if response.status() == 429 {
176 let retry_after = response
177 .headers()
178 .get("Retry-After")
179 .and_then(|v| v.to_str().ok())
180 .and_then(|v| v.parse::<u64>().ok());
181 return Err(Error::RateLimited {
182 retry_after,
183 circuit_breaker: self.breaker.is_open().await,
184 });
185 }
186
187 self.breaker.record_success().await;
188 Ok(response)
189 }
190
191 async fn handle_response<T: DeserializeOwned>(&self, resp: Response) -> Result<T> {
192 let status = resp.status();
193 if status.is_success() {
194 Ok(resp.json::<T>().await?)
195 } else {
196 let code = status.as_u16();
197 let body = resp.text().await.unwrap_or_default();
198 let message = serde_json::from_str::<serde_json::Value>(&body)
199 .ok()
200 .and_then(|v| {
201 v.get("message")
202 .and_then(|m| m.as_str())
203 .map(|s| s.to_string())
204 })
205 .unwrap_or(body);
206 Err(Error::Api {
207 status: code,
208 message,
209 })
210 }
211 }
212
213 async fn handle_no_content(&self, resp: Response) -> Result<()> {
214 let status = resp.status();
215 if status.is_success() {
216 Ok(())
217 } else {
218 let code = status.as_u16();
219 let message = resp.text().await.unwrap_or_else(|_| status.to_string());
220 Err(Error::Api {
221 status: code,
222 message,
223 })
224 }
225 }
226
227 pub async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T> {
230 let u = Arc::new(self.url(path));
231 let v = self.api_version.clone();
232 let resp = self
233 .execute_with_retry(|client| {
234 client
235 .get(u.as_str())
236 .header("Version", &v)
237 .header("Content-Type", "application/json")
238 })
239 .await?;
240 self.handle_response(resp).await
241 }
242
243 pub async fn get_with_query<T: DeserializeOwned, Q: Serialize + ?Sized>(
244 &self,
245 path: &str,
246 query: &Q,
247 ) -> Result<T> {
248 let u = Arc::new(self.url(path));
249 let v = self.api_version.clone();
250 let qv = serde_json::to_value(query).map_err(Error::Json)?;
252 let resp = self
253 .execute_with_retry(move |client| {
254 client
255 .get(u.as_str())
256 .header("Version", &v)
257 .header("Content-Type", "application/json")
258 .query(&qv)
259 })
260 .await?;
261 self.handle_response(resp).await
262 }
263
264 pub async fn post<T: DeserializeOwned, B: Serialize>(&self, path: &str, body: &B) -> Result<T> {
265 let u = Arc::new(self.url(path));
266 let v = self.api_version.clone();
267 let body_json = serde_json::to_value(body).map_err(Error::Json)?;
268 let resp = self
269 .execute_with_retry(move |client| {
270 client
271 .post(u.as_str())
272 .header("Version", &v)
273 .json(&body_json)
274 })
275 .await?;
276 self.handle_response(resp).await
277 }
278
279 pub async fn put<T: DeserializeOwned, B: Serialize>(&self, path: &str, body: &B) -> Result<T> {
280 let u = Arc::new(self.url(path));
281 let v = self.api_version.clone();
282 let body_json = serde_json::to_value(body).map_err(Error::Json)?;
283 let resp = self
284 .execute_with_retry(move |client| {
285 client
286 .put(u.as_str())
287 .header("Version", &v)
288 .json(&body_json)
289 })
290 .await?;
291 self.handle_response(resp).await
292 }
293
294 pub async fn patch<T: DeserializeOwned, B: Serialize>(
295 &self,
296 path: &str,
297 body: &B,
298 ) -> Result<T> {
299 let u = Arc::new(self.url(path));
300 let v = self.api_version.clone();
301 let body_json = serde_json::to_value(body).map_err(Error::Json)?;
302 let resp = self
303 .execute_with_retry(move |client| {
304 client
305 .patch(u.as_str())
306 .header("Version", &v)
307 .json(&body_json)
308 })
309 .await?;
310 self.handle_response(resp).await
311 }
312
313 pub async fn delete<T: DeserializeOwned>(&self, path: &str) -> Result<T> {
314 let u = Arc::new(self.url(path));
315 let v = self.api_version.clone();
316 let resp = self
317 .execute_with_retry(|client| client.delete(u.as_str()).header("Version", &v))
318 .await?;
319 self.handle_response(resp).await
320 }
321
322 pub async fn delete_with_body<T: DeserializeOwned, B: Serialize>(
323 &self,
324 path: &str,
325 body: &B,
326 ) -> Result<T> {
327 let u = Arc::new(self.url(path));
328 let v = self.api_version.clone();
329 let body_json = serde_json::to_value(body).map_err(Error::Json)?;
330 let resp = self
331 .execute_with_retry(move |client| {
332 client
333 .delete(u.as_str())
334 .header("Version", &v)
335 .json(&body_json)
336 })
337 .await?;
338 self.handle_response(resp).await
339 }
340
341 pub async fn delete_no_content(&self, path: &str) -> Result<()> {
342 let u = Arc::new(self.url(path));
343 let v = self.api_version.clone();
344 let resp = self
345 .execute_with_retry(|client| client.delete(u.as_str()).header("Version", &v))
346 .await?;
347 self.handle_no_content(resp).await
348 }
349
350 pub async fn get_raw(&self, path: &str) -> Result<Response> {
353 let u = Arc::new(self.url(path));
354 let v = self.api_version.clone();
355 self.execute_with_retry(|client| {
356 client
357 .get(u.as_str())
358 .header("Version", &v)
359 .header("Content-Type", "application/json")
360 })
361 .await
362 }
363
364 pub async fn post_raw<B: Serialize>(&self, path: &str, body: &B) -> Result<Response> {
365 let u = Arc::new(self.url(path));
366 let v = self.api_version.clone();
367 let body_json = serde_json::to_value(body).map_err(Error::Json)?;
368 self.execute_with_retry(move |client| {
369 client
370 .post(u.as_str())
371 .header("Version", &v)
372 .json(&body_json)
373 })
374 .await
375 }
376}
377
378fn exponential_backoff(retry: u32) -> Duration {
379 let base_ms = 200u64 * 2u64.pow(retry);
380 Duration::from_millis(base_ms.min(10_000))
381}
382
383fn jitter() -> Duration {
384 let t = SystemTime::now()
385 .duration_since(UNIX_EPOCH)
386 .map(|d| d.as_nanos() as u64)
387 .unwrap_or(0);
388 let hash = t
389 .wrapping_mul(6364136223846793005)
390 .wrapping_add(1442695040888963407);
391 Duration::from_millis(hash % 200)
392}