backoff 0.1.6

Retry operations with exponential backoff policy.
Documentation
use std::time::Duration;
use std::thread;

use crate::error::Error;
use crate::backoff::Backoff;

/// Operation is an operation that can be retried if it fails.
///
/// [`Operation`]: backoff/trait.Operation.html#tymethod.next_backoff
/// [`retry`]: backoff/trait.Operation.html#tymethod.retry
/// [`retry_notify`]: backoff/trait.Operation.html#tymethod.retry_notify
///
/// Operation is an operation that can be retried if it fails.
pub trait Operation<T, E> {
    /// call_op implements the effective operation.
    fn call_op(&mut self) -> Result<T, Error<E>>;

    /// Retries this operation according to the backoff policy.
    /// backoff is reset before it is used.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use backoff::{ExponentialBackoff, Operation, Error};
    /// let mut f = || -> Result<(), Error<&str>> {
    ///     // Business logic...
    ///     // Give up.
    ///     Err(Error::Permanent("error"))
    /// };
    ///
    /// let mut backoff = ExponentialBackoff::default();
    /// let _ = f.retry(&mut backoff).err().unwrap();
    /// ```
    fn retry<B>(&mut self, backoff: &mut B) -> Result<T, Error<E>>
        where B: Backoff
    {
        let nop = |_, _| ();
        self.retry_notify(backoff, nop)
    }

    /// Retries this operation according to the backoff policy.
    /// Calls notify on failed attempts (in case of transient errors).
    /// backoff is reset before it is used.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use backoff::{Operation, Error};
    /// # use backoff::backoff::Stop;
    /// # use std::time::Duration;
    /// let notify = |err, dur| { println!("Error happened at {:?}: {}", dur, err); };
    /// let mut f = || -> Result<(), Error<&str>> {
    ///     // Business logic...
    ///     Err(Error::Transient("error"))
    /// };
    ///
    /// let mut backoff = Stop{};
    /// let _ = f.retry_notify(&mut backoff, notify).err().unwrap();
    /// ```
    fn retry_notify<B, N>(&mut self, backoff: &mut B, mut notify: N) -> Result<T, Error<E>>
        where N: Notify<E>,
              B: Backoff
    {
        backoff.reset();

        loop {
            let err = match self.call_op() {
                Ok(v) => return Ok(v),
                Err(err) => err,
            };

            let err = match err {
                Error::Permanent(err) => return Err(Error::Permanent(err)),
                Error::Transient(err) => err,
            };

            let next = match backoff.next_backoff() {
                Some(next) => next,
                None => return Err(Error::Transient(err)),
            };

            notify.notify(err, next);
            thread::sleep(next);
        }
    }
}


impl<T, E, F> Operation<T, E> for F
    where F: FnMut() -> Result<T, Error<E>>
{
    fn call_op(&mut self) -> Result<T, Error<E>> {
        self()
    }
}

/// Notify is called in [`retry_notify`](trait.Operation.html#method.retry_notify) in case of errors.
pub trait Notify<E> {
    fn notify(&mut self, err: E, duration: Duration);
}

impl<E, F> Notify<E> for F
    where F: Fn(E, Duration)
{
    fn notify(&mut self, err: E, duration: Duration) {
        self(err, duration)
    }
}