#![forbid(unsafe_code)]
#![deny(clippy::unwrap_used)]
pub mod delay;
pub mod delay_executor;
pub mod delay_strategy;
mod duration;
mod fallible;
mod tracked_iterator;
use std::fmt::Debug;
use std::marker::PhantomData;
#[cfg(feature = "async")]
use crate::delay_executor::AsyncDelayExecutor;
use crate::delay_executor::DelayExecutor;
use crate::delay_executor::ThreadSleep;
#[cfg(feature = "async-tokio")]
use crate::delay_executor::TokioSleep;
use crate::delay_strategy::DelayStrategy;
pub use duration::IntoStdDuration;
pub use duration::StdDuration;
pub use fallible::NeedsRetry;
#[tracing::instrument(level = "debug", name = "retry", skip(operation))]
#[must_use = "Call `delayed_by` on the returned value to complete the retry strategy configuration."]
pub fn retry<Out, Op>(operation: Op) -> NeedsDelayStrategy<Out, Op>
where
Out: NeedsRetry + Debug,
Op: Fn() -> Out,
{
NeedsDelayStrategy { operation }
}
pub struct NeedsDelayStrategy<Out, Op>
where
Out: NeedsRetry + Debug,
Op: Fn() -> Out,
{
operation: Op,
}
impl<Out, Op> NeedsDelayStrategy<Out, Op>
where
Out: NeedsRetry + Debug,
Op: Fn() -> Out,
{
pub fn delayed_by<DelayStrat>(self, delay: DelayStrat) -> Out
where
DelayStrat: DelayStrategy<StdDuration>,
{
retry_with_options(
self.operation,
RetryOptions {
delay_strategy: delay,
delay_executor: ThreadSleep,
_marker: PhantomData,
},
)
}
}
#[derive(Debug)]
pub struct RetryOptions<
Delay: Debug + Clone,
DelayStrat: DelayStrategy<Delay>,
DelayExec: DelayExecutor<Delay>,
> {
pub delay_strategy: DelayStrat,
pub delay_executor: DelayExec,
pub _marker: PhantomData<Delay>,
}
#[tracing::instrument(level = "debug", name = "retry_with_options", skip(operation))]
pub fn retry_with_options<Delay, DelayStrat, DelayExec, Out, Op>(
operation: Op,
mut options: RetryOptions<Delay, DelayStrat, DelayExec>,
) -> Out
where
Delay: Debug + Clone,
DelayStrat: DelayStrategy<Delay> + Debug,
DelayExec: DelayExecutor<Delay> + Debug,
Out: NeedsRetry + Debug,
Op: Fn() -> Out,
{
let mut tries: usize = 1;
loop {
let out = operation();
match out.needs_retry() {
false => return out,
true => match options.delay_strategy.next_delay() {
Some(delay) => {
tracing::debug!(tries, delay = ?delay, "Operation was not successful. Waiting...");
options.delay_executor.delay_by(delay.clone());
tries += 1;
}
None => {
tracing::error!(tries, last_output = ?out, "Operation was not successful after maximum retries. Aborting with last output seen.");
return out;
}
},
};
}
}
#[cfg(feature = "async")]
#[tracing::instrument(level = "debug", name = "retry_async", skip(operation))]
pub fn retry_async<Out, Op>(operation: Op) -> AsyncNeedsDelayStrategy<Out, Op>
where
Out: NeedsRetry + Debug,
Op: AsyncFn() -> Out,
{
AsyncNeedsDelayStrategy { operation }
}
#[cfg(feature = "async")]
pub struct AsyncNeedsDelayStrategy<Out, Op>
where
Out: NeedsRetry + Debug,
Op: AsyncFn() -> Out,
{
operation: Op,
}
#[cfg(feature = "async")]
impl<Out, Op> AsyncNeedsDelayStrategy<Out, Op>
where
Out: NeedsRetry + Debug,
Op: AsyncFn() -> Out,
{
pub async fn delayed_by<DelayStrat>(self, delay: DelayStrat) -> Out
where
DelayStrat: DelayStrategy<StdDuration>,
{
retry_async_with_options(
self.operation,
RetryAsyncOptions {
delay_strategy: delay,
delay_executor: TokioSleep,
_marker: PhantomData,
},
)
.await
}
}
#[cfg(feature = "async")]
#[derive(Debug)]
pub struct RetryAsyncOptions<
Delay: Debug + Clone,
DelayStrat: DelayStrategy<Delay>,
DelayExec: AsyncDelayExecutor<Delay>,
> {
pub delay_strategy: DelayStrat,
pub delay_executor: DelayExec,
pub _marker: PhantomData<Delay>,
}
#[cfg(feature = "async")]
#[tracing::instrument(
level = "debug",
name = "retry_async_with_delay_strategy",
skip(operation)
)]
pub async fn retry_async_with_options<Delay, DelayStrat, DelayExec, Out>(
operation: impl AsyncFn() -> Out,
mut options: RetryAsyncOptions<Delay, DelayStrat, DelayExec>,
) -> Out
where
Delay: Debug + Clone,
DelayStrat: DelayStrategy<Delay>,
DelayExec: AsyncDelayExecutor<Delay>,
Out: NeedsRetry + Debug,
{
let mut tries: usize = 1;
loop {
let out = operation().await;
match out.needs_retry() {
false => return out,
true => match options.delay_strategy.next_delay() {
Some(delay) => {
tracing::debug!(tries, delay = ?delay, "Operation was not successful. Waiting...");
options.delay_executor.delay_by(delay.clone()).await;
tries += 1;
}
None => {
tracing::error!(tries, last_output = ?out, "Operation was not successful after maximum retries. Aborting with last output seen.");
return out;
}
},
};
}
}