use std::time::Duration;
use serde::de::DeserializeOwned;
use crate::error::{DataError, Result};
const TIMEOUT: Duration = Duration::from_secs(15);
const USER_AGENT: &str = concat!("wc-data/", env!("CARGO_PKG_VERSION"));
#[derive(Debug, Clone)]
pub struct Http {
client: reqwest::Client,
}
impl Http {
pub fn new() -> Result<Self> {
let client = reqwest::Client::builder()
.timeout(TIMEOUT)
.user_agent(USER_AGENT)
.build()
.map_err(|err| DataError::Transport(err.to_string()))?;
Ok(Self { client })
}
#[must_use]
pub fn with_client(client: reqwest::Client) -> Self {
Self { client }
}
pub async fn get_json<T: DeserializeOwned>(
&self,
url: &str,
headers: &[(&str, &str)],
) -> Result<T> {
let bytes = self.get_bytes(url, headers).await?;
serde_json::from_slice(&bytes).map_err(|err| DataError::Decode(err.to_string()))
}
pub async fn get_bytes(&self, url: &str, headers: &[(&str, &str)]) -> Result<Vec<u8>> {
let mut req = self.client.get(url);
for (name, value) in headers {
req = req.header(*name, *value);
}
let resp = req
.send()
.await
.map_err(|err| DataError::Transport(err.to_string()))?;
let status = resp.status();
if status.as_u16() == 429 {
let retry_after = resp
.headers()
.get(reqwest::header::RETRY_AFTER)
.and_then(|v| v.to_str().ok())
.and_then(|v| v.parse::<u64>().ok());
return Err(DataError::RateLimited { retry_after });
}
if !status.is_success() {
let message = resp
.text()
.await
.ok()
.map(|body| body.chars().take(200).collect::<String>())
.filter(|s| !s.is_empty())
.unwrap_or_else(|| status.canonical_reason().unwrap_or("error").to_owned());
return Err(DataError::Status {
status: status.as_u16(),
message,
});
}
resp.bytes()
.await
.map(|b| b.to_vec())
.map_err(|err| DataError::Transport(err.to_string()))
}
}