use std::marker::PhantomData;
use anyhow::Result;
use serde::{de::DeserializeOwned, Serialize};
pub use ::http::Method;
pub struct HttpClient<T: ?Sized> {
base_url: String,
client: reqwest::Client,
_marker: PhantomData<T>,
}
impl<T: ?Sized> Clone for HttpClient<T> {
fn clone(&self) -> Self {
Self {
base_url: self.base_url.clone(),
client: self.client.clone(),
_marker: PhantomData,
}
}
}
impl<T: ?Sized + 'static> HttpClient<T> {
pub fn new(base_url: impl Into<String>) -> Self {
Self {
base_url: base_url.into().trim_end_matches('/').to_string(),
client: reqwest::Client::new(),
_marker: PhantomData,
}
}
pub fn with_client(base_url: impl Into<String>, client: reqwest::Client) -> Self {
Self {
base_url: base_url.into().trim_end_matches('/').to_string(),
client,
_marker: PhantomData,
}
}
pub async fn request<B, R>(
&self,
method: Method,
path: &str,
query: &[(&str, String)],
body: Option<&B>,
) -> Result<R>
where
B: Serialize,
R: DeserializeOwned,
{
let url = format!("{}{}", self.base_url, path);
let mut request = self.client.request(method.clone(), &url);
if !query.is_empty() {
request = request.query(query);
}
if let Some(body) = body {
request = request.json(body);
}
let response = request.send().await?;
if !response.status().is_success() {
let status = response.status();
let text = response.text().await.unwrap_or_default();
anyhow::bail!("HTTP {} {}: {}", status.as_u16(), status.as_str(), text);
}
let result: R = response.json().await?;
Ok(result)
}
}