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
126
127
128
129
130
//
// Copyright (c) Dell Inc., or its subsidiaries. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//

use snafu::Snafu;
use std::fmt::Debug;
use std::time::Duration;

/// The RetryResult that the operation should return.
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
pub enum RetryResult<T, E> {
    /// Contains the return value if the operation succeed.
    Success(T),
    /// Contains the error value if duration is exceeded.
    Retry(E),
    /// Contains an error value to return immediately.
    Fail(E),
}

/// An error that the Retry function would give.
#[derive(Debug, PartialEq, Eq, Snafu)]
#[snafu(display(
    "Failed after {} retries due to {} which took {:?}",
    tries,
    error,
    total_delay
))]
pub struct RetryError<E: std::fmt::Display> {
    /// The error returned by the operation on the last try.
    pub error: E,
    /// The duration spent waiting between retries of the operation.
    pub total_delay: Duration,
    /// The total number of times the operation was tried.
    pub tries: u64,
}

///
/// Trait which is used check if the Error is Retryable.
///
pub trait Retryable {
    fn can_retry(&self) -> bool;
}

///
/// `wrap_with_async_retry!` macro wraps any arbitrary async function with `pravega_rust_client_retry::retry_async::retry_async`
/// This macro takes two parameters. The first parameter is the Retry policy which implements `trait BackoffSchedule`.
/// The second parameter is the async function that needs to be wrapped within the retry logic.
/// The function invocation will be retried only error returned by the function returns `can_retry()` as true.
///
/// E.g: usage
///
/// ```ignore
/// use pravega_rust_client_retry::retry_policy::RetryWithBackoff;
/// use pravega_rust_client_retry::retry_async::retry_async;
/// use pravega_rust_client_retry::wrap_with_async_retry;
/// //CustomError implements Retrayable trait
/// async fn function_a(param1: &str, param2:u8) -> Result<(), CustomError> {
///
/// }
/// let retry_policy = RetryWithBackoff::default().max_tries(10);
/// // the below invocation wraps function_a with the retry logic.
/// wrap_with_async_retry!(retry_policy, function_a("test", 1));
/// ```
///
#[macro_export]
macro_rules! wrap_with_async_retry {
    ($retrypolicy:expr, $expression:expr) => {
        retry_async($retrypolicy, || async {
            let r = $expression.await;
            match r {
                Ok(res) => RetryResult::Success(res),
                Err(e) => {
                    if e.can_retry() {
                        RetryResult::Retry(e)
                    } else {
                        RetryResult::Fail(e)
                    }
                }
            }
        })
        .await
    };
}

///
/// `wrap_with_sync_retry!` macro wraps any arbitrary synchronous function with `pravega_rust_client_retry::retry_sync::retry_sync`
/// This macro takes two parameters. The first parameter is the Retry policy which implements `trait BackoffSchedule`.
/// The second parameter is the synchrounous function that needs to be wrapped within the retry logic.
/// The function invocation will be retried only error returned by the function returns `can_retry()` as true.
///
/// E.g: usage
///
/// ```ignore
/// use pravega_rust_client_retry::retry_policy::RetryWithBackoff;
/// use pravega_rust_client_retry::retry_sync::retry_sync;
/// use pravega_rust_client_retry::wrap_with_sync_retry;
/// // CustomError implements Retryable trait
/// fn function_a(param1: &str, param2:u8) -> Result<(), CustomError>{
///
/// }
/// let retry_policy = RetryWithBackoff::default().max_tries(5);
/// // the below invocation wraps function_a with the retry logic.
/// wrap_with_sync_retry!(retry_policy, function_a("test", 1));
/// ```
///
#[macro_export]
macro_rules! wrap_with_sync_retry {
    ($retrypolicy:expr, $expression:expr) => {
        retry_sync($retrypolicy, || {
            let r = $expression;
            match r {
                Ok(res) => RetryResult::Success(res),
                Err(e) => {
                    if e.can_retry() {
                        RetryResult::Retry(e)
                    } else {
                        RetryResult::Fail(e)
                    }
                }
            }
        })
        .await
    };
}