#![deny(clippy::all, missing_docs, unused_crate_dependencies)]
#[cfg(feature = "blocking")] pub mod blocking;
pub mod error;
use error::*;
#[cfg(test)] mod test;
#[cfg(feature = "reqwest")] pub use reqwest;
use std::{future::Future, sync::LazyLock, time::Duration};
#[cfg(feature = "reqwest")] use reqwest::{Client, Request, Response};
use tokio::time;
pub trait Http
where
Self: Send + Sync,
{
type Request: Send + TryClone;
type Response: Send;
fn request(
&self,
request: Self::Request,
) -> impl Send + Future<Output = Result<Self::Response>>;
fn request_with_retries(
&self,
request: Self::Request,
retries: u32,
retry_delay_ms: u64,
) -> impl Send + Future<Output = Result<Self::Response>> {
async move {
for i in 1..=retries {
match self.request(request.try_clone().ok_or(Error::NonRetriableRequest)?).await {
Ok(r) => return Ok(r),
Err(e) => {
tracing::error!(
"attempt {i}/{retries}, {e:?}, retrying in {retry_delay_ms}ms"
);
time::sleep(Duration::from_millis(retry_delay_ms)).await;
},
}
}
Err(Error::ExceededMaxRetries(retries))?
}
}
}
#[cfg(feature = "reqwest")]
impl Http for Client {
type Request = Request;
type Response = Response;
fn request(
&self,
request: Self::Request,
) -> impl Send + Future<Output = Result<Self::Response>> {
async move { Ok(self.execute(request).await?) }
}
}
pub trait TryClone
where
Self: Sized,
{
fn try_clone(&self) -> Option<Self>;
}
#[cfg(feature = "reqwest")]
impl TryClone for Request {
fn try_clone(&self) -> Option<Self> {
self.try_clone()
}
}
pub const fn lazy<F, C>(f: F) -> LazyLock<C, F>
where
F: FnOnce() -> C,
{
LazyLock::new(f)
}