1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//! Middleware for tokio services that adds automatic retries
//! in case of failure.
//!
//! # Examples
//!
//! ```rust
//! extern crate futures;
//! extern crate tokio_core;
//! extern crate tokio_service;
//! extern crate tokio_retry;
//!
//! use std::io;
//!
//! use futures::{BoxFuture, Future, future};
//! use tokio_core::reactor::Core;
//! use tokio_service::Service;
//! use tokio_retry::Middleware;
//! use tokio_retry::strategy::{ExponentialBackoff, jitter};
//!
//! struct EchoService;
//!
//! impl Service for EchoService {
//!    type Request = String;
//!    type Response = String;
//!    type Error = ();
//!    type Future = BoxFuture<String, ()>;
//!    fn call(&self, input: String) -> Self::Future {
//!        future::ok(input).boxed()
//!    }
//! }
//!
//! fn main() {
//!     let mut core = Core::new().unwrap();
//!
//!     let retry_strategy = || ExponentialBackoff::from_millis(10)
//!         .map(jitter)
//!         .take(3);
//!
//!     let retry_service = Middleware::new(core.handle(), retry_strategy, EchoService);
//!     let retry_result = core.run(retry_service.call("hello world!".to_string()));
//!
//!     assert_eq!(retry_result, Ok("hello world!".to_string()));
//! }
//! ```

use std::iter::{Iterator, IntoIterator};
use std::time::Duration;
use std::sync::Arc;
use tokio_service::Service;
use tokio_core::reactor::Handle;

use super::{Retry, Error};
use super::action::Action;

/// Represents a retryable request to a service.
pub struct ServiceRequest<S: Service> {
    inner: Arc<S>,
    request: S::Request
}

impl<S: Service> Action for ServiceRequest<S> where S::Request: Clone {
    type Error = S::Error;
    type Item = S::Response;
    type Future = S::Future;

    fn run(&mut self) -> Self::Future {
        self.inner.call(self.request.clone())
    }
}

/// Middleware that adds retries to a service via a retry strategy.
pub struct Middleware<T, S> {
    inner: Arc<S>,
    handle: Handle,
    strategy: T
}

/// Trait to produce iterators that will be used as retry strategies.
///
/// Can be implemented directly, but the simplest way to instantiate
/// a strategy factory is by leveraging the `impl` for `Fn()`:
///
/// ```rust
/// # use tokio_retry::strategy::ExponentialBackoff;
/// let retry_strategy = || ExponentialBackoff::from_millis(10);
/// ```
pub trait StrategyFactory {
    type Iter: Iterator<Item=Duration>;

    fn get_strategy(&self) -> Self::Iter;
}

impl<F, I: IntoIterator<Item=Duration>> StrategyFactory for F where F: Fn() -> I {
    type Iter = I::IntoIter;

    fn get_strategy(&self) -> Self::Iter {
        self().into_iter()
    }
}

impl<T: StrategyFactory, S> Middleware<T, S> {
    pub fn new(handle: Handle, strategy: T, inner: S) -> Middleware<T, S> {
        Middleware{
            inner: Arc::new(inner),
            handle: handle,
            strategy: strategy
        }
    }
}

impl<T: StrategyFactory, S: Service> Service for Middleware<T, S> where S::Request: Clone {
    type Request = S::Request;
    type Response = S::Response;
    type Error = Error<S::Error>;
    type Future = Retry<T::Iter, ServiceRequest<S>>;

    fn call(&self, request: Self::Request) -> Self::Future {
        let action = ServiceRequest{
            inner: self.inner.clone(),
            request: request
        };

        Retry::spawn(self.handle.clone(), self.strategy.get_strategy(), action)
    }
}