use std::time::Duration;
use async_trait::async_trait;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Method {
Get,
Post,
Patch,
Delete,
}
#[derive(Clone, Debug)]
pub struct HttpRequest {
pub method: Method,
pub url: String,
pub headers: Vec<(String, String)>,
pub body: Option<Vec<u8>>,
}
#[derive(Clone, Debug)]
pub struct HttpResponse {
pub status: u16,
pub headers: Vec<(String, String)>,
pub body: Vec<u8>,
}
#[derive(Clone, Debug)]
pub struct TransportError {
pub message: String,
pub timeout: bool,
}
#[async_trait]
pub trait Transport: Send + Sync {
async fn execute(&self, request: HttpRequest) -> Result<HttpResponse, TransportError>;
}
#[async_trait]
pub trait Sleeper: Send + Sync {
async fn sleep(&self, duration: Duration);
fn jitter(&self) -> f64;
}
pub(crate) struct ReqwestTransport {
client: reqwest::Client,
}
impl ReqwestTransport {
pub(crate) fn new(timeout: Duration) -> Result<Self, TransportError> {
let client = reqwest::Client::builder()
.timeout(timeout)
.build()
.map_err(|e| TransportError {
message: e.to_string(),
timeout: false,
})?;
Ok(Self { client })
}
}
#[async_trait]
impl Transport for ReqwestTransport {
async fn execute(&self, request: HttpRequest) -> Result<HttpResponse, TransportError> {
let method = match request.method {
Method::Get => reqwest::Method::GET,
Method::Post => reqwest::Method::POST,
Method::Patch => reqwest::Method::PATCH,
Method::Delete => reqwest::Method::DELETE,
};
let mut builder = self.client.request(method, &request.url);
for (name, value) in &request.headers {
builder = builder.header(name, value);
}
if let Some(body) = request.body {
builder = builder.body(body);
}
let response = builder.send().await.map_err(|e| TransportError {
message: e.to_string(),
timeout: e.is_timeout(),
})?;
let status = response.status().as_u16();
let headers = response
.headers()
.iter()
.map(|(name, value)| {
(
name.as_str().to_string(),
value.to_str().unwrap_or("").to_string(),
)
})
.collect();
let body = response
.bytes()
.await
.map_err(|e| TransportError {
message: e.to_string(),
timeout: e.is_timeout(),
})?
.to_vec();
Ok(HttpResponse {
status,
headers,
body,
})
}
}
pub(crate) struct RealSleeper;
#[async_trait]
impl Sleeper for RealSleeper {
async fn sleep(&self, duration: Duration) {
if !duration.is_zero() {
tokio::time::sleep(duration).await;
}
}
fn jitter(&self) -> f64 {
fastrand::f64()
}
}