use reqwest::blocking::Client;
use std::time::Duration;
use crate::error::Result;
#[derive(Clone, Debug)]
pub struct HttpClient {
pub inner: Client,
pub base_url: String,
}
impl HttpClient {
pub fn new(timeout: Duration, base_url: impl Into<String>) -> Self {
let inner = Client::builder()
.timeout(timeout)
.user_agent("animedb/0.1")
.build()
.expect("reqwest blocking client must build");
Self {
inner,
base_url: base_url.into(),
}
}
pub fn standard() -> Self {
Self::new(Duration::from_secs(30), "")
}
pub fn get(&self, path: &str) -> reqwest::blocking::RequestBuilder {
self.inner.get(format!("{}{}", self.base_url, path))
}
pub fn post(&self, path: &str) -> reqwest::blocking::RequestBuilder {
self.inner.post(format!("{}{}", self.base_url, path))
}
pub fn with_base_url(mut self, base_url: impl Into<String>) -> Self {
self.base_url = base_url.into();
self
}
}
pub fn with_retry<F>(
max_retries: u32,
initial_delay: Duration,
mut request_fn: F,
) -> Result<reqwest::blocking::Response>
where
F: FnMut() -> Result<reqwest::blocking::Response>,
{
let mut delay = initial_delay;
for attempt in 0..=max_retries {
let response = request_fn()?;
if response.status() != reqwest::StatusCode::TOO_MANY_REQUESTS {
return Ok(response.error_for_status()?);
}
if attempt == max_retries {
return Ok(response.error_for_status()?);
}
if let Some(retry_after) = response.headers().get(reqwest::header::RETRY_AFTER)
&& let Ok(secs) = retry_after.to_str().unwrap_or("").parse::<u64>()
{
delay = Duration::from_secs(secs + 1);
}
std::thread::sleep(delay);
delay *= 2;
}
unreachable!("loop always returns inside")
}
#[inline]
pub fn clamp_page_size(page_size: usize, max: usize) -> usize {
page_size.clamp(1, max)
}
#[inline]
pub fn page_to_offset(cursor_page: usize, page_size: usize) -> usize {
cursor_page.saturating_sub(1) * page_size
}