diff --git a/tower-retry/Cargo.toml b/tower-retry/Cargo.toml index 71c48cc87..2d29f43e4 100644 --- a/tower-retry/Cargo.toml +++ b/tower-retry/Cargo.toml @@ -27,9 +27,9 @@ tower-layer = "0.3" tokio = { version = "0.2", features = ["time"] } pin-project = "0.4" futures-core = { version = "0.3", default-features = false } +futures-util = { version = "0.3", default-features = false } [dev-dependencies] tower-test = { version = "0.3", path = "../tower-test" } tokio = { version = "0.2", features = ["macros", "test-util"] } tokio-test = "0.2" -futures-util = { version = "0.3", default-features = false } diff --git a/tower-retry/src/lib.rs b/tower-retry/src/lib.rs index c35618e92..e7e58c5f1 100644 --- a/tower-retry/src/lib.rs +++ b/tower-retry/src/lib.rs @@ -12,9 +12,11 @@ pub mod budget; pub mod future; mod layer; +mod policies; mod policy; pub use crate::layer::RetryLayer; +pub use crate::policies::{RetryErrors, RetryLimit}; pub use crate::policy::Policy; use crate::future::ResponseFuture; diff --git a/tower-retry/src/policies.rs b/tower-retry/src/policies.rs new file mode 100644 index 000000000..6b4287344 --- /dev/null +++ b/tower-retry/src/policies.rs @@ -0,0 +1,61 @@ +use super::Policy; +use futures_util::future; + +/// A very basic retry policy with a limited number of retry attempts. +/// +/// FIXME: explain why not to use this. +#[derive(Clone, Debug)] +pub struct RetryLimit { + remaining_tries: usize, +} + +impl RetryLimit { + /// Create a policy with the given number of retry attempts. + pub fn new(retry_attempts: usize) -> Self { + RetryLimit { + remaining_tries: retry_attempts, + } + } +} + +impl Policy for RetryLimit { + type Future = future::Ready; + fn retry(&self, _: &Req, result: Result<&Res, &E>) -> Option { + if result.is_err() { + if self.remaining_tries > 0 { + Some(future::ready(RetryLimit { + remaining_tries: self.remaining_tries - 1, + })) + } else { + None + } + } else { + None + } + } + + fn clone_request(&self, req: &Req) -> Option { + Some(req.clone()) + } +} + +/// A very basic retry policy that always retries failed requests. +/// +/// FIXME: explain why not to use this. +#[derive(Clone, Debug)] +pub struct RetryErrors; + +impl Policy for RetryErrors { + type Future = future::Ready; + fn retry(&self, _: &Req, result: Result<&Res, &E>) -> Option { + if result.is_err() { + Some(future::ready(RetryErrors)) + } else { + None + } + } + + fn clone_request(&self, req: &Req) -> Option { + Some(req.clone()) + } +} diff --git a/tower-retry/src/policy.rs b/tower-retry/src/policy.rs index 6f4c16dda..a52063b1c 100644 --- a/tower-retry/src/policy.rs +++ b/tower-retry/src/policy.rs @@ -1,47 +1,6 @@ use std::future::Future; /// A "retry policy" to classify if a request should be retried. -/// -/// # Example -/// -/// ``` -/// use tower_retry::Policy; -/// use futures_util::future; -/// -/// type Req = String; -/// type Res = String; -/// -/// struct Attempts(usize); -/// -/// impl Policy for Attempts { -/// type Future = future::Ready; -/// -/// fn retry(&self, req: &Req, result: Result<&Res, &E>) -> Option { -/// match result { -/// Ok(_) => { -/// // Treat all `Response`s as success, -/// // so don't retry... -/// None -/// }, -/// Err(_) => { -/// // Treat all errors as failures... -/// // But we limit the number of attempts... -/// if self.0 > 0 { -/// // Try again! -/// Some(future::ready(Attempts(self.0 - 1))) -/// } else { -/// // Used all our attempts, no retry... -/// None -/// } -/// } -/// } -/// } -/// -/// fn clone_request(&self, req: &Req) -> Option { -/// Some(req.clone()) -/// } -/// } -/// ``` pub trait Policy: Sized { /// The `Future` type returned by `Policy::retry()`. type Future: Future; @@ -50,9 +9,9 @@ pub trait Policy: Sized { /// This method is passed a reference to the original request, and either /// the `Service::Response` or `Service::Error` from the inner service. /// - /// If the request should **not** be retried, return `None`. + /// If the request **should not** be retried, return `None`. /// - /// If the request *should* be retried, return `Some` future of a new + /// If the request **should** be retried, return `Some` future of a new /// policy that would apply for the next request attempt. fn retry(&self, req: &Req, result: Result<&Res, &E>) -> Option; /// Tries to clone a request before being passed to the inner service. diff --git a/tower-retry/tests/retry.rs b/tower-retry/tests/retry.rs index be45ed943..e3e7bd1a2 100644 --- a/tower-retry/tests/retry.rs +++ b/tower-retry/tests/retry.rs @@ -1,6 +1,6 @@ use futures_util::future; use tokio_test::{assert_pending, assert_ready_err, assert_ready_ok, task}; -use tower_retry::Policy; +use tower_retry::{Policy, RetryErrors, RetryLimit}; use tower_test::{assert_request_eq, mock}; #[tokio::test] @@ -22,7 +22,7 @@ async fn retry_errors() { #[tokio::test] async fn retry_limit() { - let (mut service, mut handle) = new_service(Limit(2)); + let (mut service, mut handle) = new_service(RetryLimit::new(2)); assert_ready_ok!(service.poll_ready()); @@ -83,42 +83,6 @@ type Error = Box; type Mock = mock::Mock; type Handle = mock::Handle; -#[derive(Clone)] -struct RetryErrors; - -impl Policy for RetryErrors { - type Future = future::Ready; - fn retry(&self, _: &Req, result: Result<&Res, &Error>) -> Option { - if result.is_err() { - Some(future::ready(RetryErrors)) - } else { - None - } - } - - fn clone_request(&self, req: &Req) -> Option { - Some(*req) - } -} - -#[derive(Clone)] -struct Limit(usize); - -impl Policy for Limit { - type Future = future::Ready; - fn retry(&self, _: &Req, result: Result<&Res, &Error>) -> Option { - if result.is_err() && self.0 > 0 { - Some(future::ready(Limit(self.0 - 1))) - } else { - None - } - } - - fn clone_request(&self, req: &Req) -> Option { - Some(*req) - } -} - #[derive(Clone)] struct UnlessErr(InnerError);