Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow FnMut(..) on notify and when #54

Merged
merged 5 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 60 additions & 10 deletions src/blocking_retry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,19 @@ where
}

/// Retry struct generated by [`Retryable`].
pub struct BlockingRetry<B: Backoff, T, E, F: FnMut() -> Result<T, E>> {
pub struct BlockingRetry<
B: Backoff,
T,
E,
F: FnMut() -> Result<T, E>,
RF = fn(&E) -> bool,
NF = fn(&E, Duration),
> {
backoff: B,
retryable: fn(&E) -> bool,
notify: fn(&E, Duration),
retryable: RF,
notify: NF,
f: F,
}

impl<B, T, E, F> BlockingRetry<B, T, E, F>
where
B: Backoff,
Expand All @@ -79,7 +85,15 @@ where
f,
}
}
}

impl<B, T, E, F, RF, NF> BlockingRetry<B, T, E, F, RF, NF>
where
B: Backoff,
F: FnMut() -> Result<T, E>,
RF: FnMut(&E) -> bool,
NF: FnMut(&E, Duration),
{
/// Set the conditions for retrying.
///
/// If not specified, we treat all errors as retryable.
Expand All @@ -105,9 +119,13 @@ where
/// Ok(())
/// }
/// ```
pub fn when(mut self, retryable: fn(&E) -> bool) -> Self {
self.retryable = retryable;
self
pub fn when<RN: FnMut(&E) -> bool>(self, retryable: RN) -> BlockingRetry<B, T, E, F, RN, NF> {
BlockingRetry {
backoff: self.backoff,
retryable,
notify: self.notify,
f: self.f,
}
}

/// Set to notify for everything retrying.
Expand Down Expand Up @@ -140,9 +158,13 @@ where
/// Ok(())
/// }
/// ```
pub fn notify(mut self, notify: fn(&E, Duration)) -> Self {
self.notify = notify;
self
pub fn notify<NN: FnMut(&E, Duration)>(self, notify: NN) -> BlockingRetry<B, T, E, F, RF, NN> {
BlockingRetry {
backoff: self.backoff,
retryable: self.retryable,
notify,
f: self.f,
}
}

/// Call the retried function.
Expand Down Expand Up @@ -245,4 +267,32 @@ mod tests {
assert_eq!(*error_times.lock().unwrap(), 4);
Ok(())
}

#[test]
fn test_fn_mut_when_and_notify() -> anyhow::Result<()> {
let mut calls_retryable: Vec<()> = vec![];
let mut calls_notify: Vec<()> = vec![];

let f = || Err::<(), anyhow::Error>(anyhow::anyhow!("retryable"));

let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1));
let result = f
.retry(&backoff)
.when(|_| {
calls_retryable.push(());
true
})
.notify(|_, _| {
calls_notify.push(());
})
.call();

assert!(result.is_err());
assert_eq!("retryable", result.unwrap_err().to_string());
// `f` always returns error "retryable", so it should be executed
// 4 times (retry 3 times).
assert_eq!(calls_retryable.len(), 4);
assert_eq!(calls_notify.len(), 3);
Ok(())
}
}
83 changes: 73 additions & 10 deletions src/retry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,18 @@ where

/// Retry struct generated by [`Retryable`].
#[pin_project]
pub struct Retry<B: Backoff, T, E, Fut: Future<Output = Result<T, E>>, FutureFn: FnMut() -> Fut> {
pub struct Retry<
B: Backoff,
T,
E,
Fut: Future<Output = Result<T, E>>,
FutureFn: FnMut() -> Fut,
RF = fn(&E) -> bool,
NF = fn(&E, Duration),
> {
backoff: B,
retryable: fn(&E) -> bool,
notify: fn(&E, Duration),
retryable: RF,
notify: NF,
future_fn: FutureFn,

#[pin]
Expand All @@ -111,7 +119,16 @@ where
state: State::Idle,
}
}
}

impl<B, T, E, Fut, FutureFn, RF, NF> Retry<B, T, E, Fut, FutureFn, RF, NF>
where
B: Backoff,
Fut: Future<Output = Result<T, E>>,
FutureFn: FnMut() -> Fut,
RF: FnMut(&E) -> bool,
NF: FnMut(&E, Duration),
{
/// Set the conditions for retrying.
///
/// If not specified, we treat all errors as retryable.
Expand Down Expand Up @@ -141,9 +158,17 @@ where
/// Ok(())
/// }
/// ```
pub fn when(mut self, retryable: fn(&E) -> bool) -> Self {
self.retryable = retryable;
self
pub fn when<RN: FnMut(&E) -> bool>(
self,
retryable: RN,
) -> Retry<B, T, E, Fut, FutureFn, RN, NF> {
Retry {
backoff: self.backoff,
retryable,
notify: self.notify,
future_fn: self.future_fn,
state: self.state,
}
}

/// Set to notify for everything retrying.
Expand Down Expand Up @@ -179,9 +204,17 @@ where
/// Ok(())
/// }
/// ```
pub fn notify(mut self, notify: fn(&E, Duration)) -> Self {
self.notify = notify;
self
pub fn notify<NN: FnMut(&E, Duration)>(
self,
notify: NN,
) -> Retry<B, T, E, Fut, FutureFn, RF, NN> {
Retry {
backoff: self.backoff,
retryable: self.retryable,
notify,
future_fn: self.future_fn,
state: self.state,
}
}
}

Expand All @@ -201,11 +234,13 @@ enum State<T, E, Fut: Future<Output = Result<T, E>>> {
Sleeping(#[pin] Pin<Box<tokio::time::Sleep>>),
}

impl<B, T, E, Fut, FutureFn> Future for Retry<B, T, E, Fut, FutureFn>
impl<B, T, E, Fut, FutureFn, RF, NF> Future for Retry<B, T, E, Fut, FutureFn, RF, NF>
where
B: Backoff,
Fut: Future<Output = Result<T, E>>,
FutureFn: FnMut() -> Fut,
RF: FnMut(&E) -> bool,
NF: FnMut(&E, Duration),
{
type Output = Result<T, E>;

Expand Down Expand Up @@ -320,4 +355,32 @@ mod tests {
assert_eq!(*error_times.lock().await, 4);
Ok(())
}

#[tokio::test]
async fn test_fn_mut_when_and_notify() -> anyhow::Result<()> {
let mut calls_retryable: Vec<()> = vec![];
let mut calls_notify: Vec<()> = vec![];

let f = || async { Err::<(), anyhow::Error>(anyhow::anyhow!("retryable")) };

let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1));
let result = f
.retry(&backoff)
.when(|_| {
calls_retryable.push(());
true
})
.notify(|_, _| {
calls_notify.push(());
})
.await;

assert!(result.is_err());
assert_eq!("retryable", result.unwrap_err().to_string());
// `f` always returns error "retryable", so it should be executed
// 4 times (retry 3 times).
assert_eq!(calls_retryable.len(), 4);
assert_eq!(calls_notify.len(), 3);
Ok(())
}
}