use crate::error::Error;
#[derive(Debug, Clone)]
pub struct HttpResponse {
pub status: u16,
pub headers: Vec<(String, String)>,
pub body: Vec<u8>,
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum Method {
Get,
Post,
}
#[async_trait::async_trait]
pub trait HttpClient: Send + Sync {
async fn request(
&self,
method: Method,
url: &str,
headers: &[(&str, &str)],
body: Option<Vec<u8>>,
) -> Result<HttpResponse, Error>;
}
#[cfg(feature = "reqwest")]
pub struct ReqwestClient {
inner: reqwest::Client,
}
#[cfg(feature = "reqwest")]
impl ReqwestClient {
pub fn new(timeout: std::time::Duration) -> Result<Self, Error> {
let inner =
reqwest::Client::builder().timeout(timeout).build().map_err(Error::HttpClient)?;
Ok(Self { inner })
}
}
#[cfg(feature = "reqwest")]
#[async_trait::async_trait]
impl HttpClient for ReqwestClient {
async fn request(
&self,
method: Method,
url: &str,
headers: &[(&str, &str)],
body: Option<Vec<u8>>,
) -> Result<HttpResponse, Error> {
let mut builder = match method {
Method::Get => self.inner.get(url),
Method::Post => self.inner.post(url),
};
for (key, value) in headers {
builder = builder.header(*key, *value);
}
if body.is_some() {
builder = builder.header("content-type", "application/json");
}
if let Some(body) = body {
builder = builder.body(body);
}
let resp = builder.send().await.map_err(|e| Error::Http(Box::new(e)))?;
let status = resp.status().as_u16();
let headers = resp
.headers()
.iter()
.map(|(k, v)| (k.as_str().to_owned(), v.to_str().unwrap_or("").to_owned()))
.collect();
let body = resp.bytes().await.map_err(|e| Error::Http(Box::new(e)))?.to_vec();
Ok(HttpResponse { status, headers, body })
}
}