pub extern crate shadow_clone;
use std::{
error::Error,
fmt::{Debug, Display},
};
#[derive(Debug)]
pub struct RetryError<T: Debug> {
pub retries: Vec<T>,
}
impl<T: Debug> Display for RetryError<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Retry Error")
}
}
impl<T: Debug> Error for RetryError<T> {}
#[macro_export]
macro_rules! retry {
($retries: expr, $f: ident$(,)* $($params:ident),* $(,)?) => {
{
(|| {
let mut errs = Vec::with_capacity($retries);
for _ in 0..$retries {
$crate::shadow_clone::shadow_clone!($($params),*);
match $f($($params),*) {
Ok(res) => return Ok(res),
Err(e) => {
errs.push(e);
}
}
}
Err(RetryError{retries: errs})
})()
}
};
}
#[macro_export]
macro_rules! retry_sleep {
($retries: expr, $time_ms: expr, $f: ident$(,)* $($params:ident),* $(,)? ) => {
{
(|| {
let mut errs = Vec::with_capacity($retries);
for _ in 0..$retries {
$crate::shadow_clone::shadow_clone!($($params)*);
match $f($($params)*) {
Ok(res) => return Ok(res),
Err(e) => {
errs.push(e);
std::thread::sleep(std::time::Duration::from_millis($time_ms))
}
}
}
Err(RetryError{retries: errs})
})()
}
};
}
#[macro_export]
macro_rules! retry_async {
($retries: expr, $f: ident$(,)* $($params:ident),* $(,)?) => {
{
let r = (async {
let mut errs = Vec::with_capacity($retries);
for _ in 0..$retries {
$crate::shadow_clone::shadow_clone!($($params),*);
match $f($($params),*).await {
Ok(res) => return Ok(res),
Err(e) => {
errs.push(e);
}
}
}
Err(RetryError {retries: errs})
}).await;
r
}
};
}
#[macro_export]
#[cfg(any(feature = "tokio", feature = "async-std"))]
macro_rules! retry_async_sleep {
($retries: expr, $time_ms: expr, $f: ident$(,)* $($params:ident),* $(,)? ) => {
{
#[cfg(all(feature = "tokio", feature = "async-std"))]
compile_error!("tokio and async-std are mutually exclusive and cannot be enabled together");
let r = (async {
let mut errs = Vec::with_capacity($retries);
for _ in 0..$retries {
$crate::shadow_clone::shadow_clone!($($params),*);
match $f($($params),*).await {
Ok(res) => return Ok(res),
Err(e) => {
errs.push(e);
#[cfg(feature = "tokio")]
tokio::time::sleep(tokio::time::Duration::from_millis($time_ms)).await;
#[cfg(feature = "async-std")]
async_std::task::sleep(std::time::Duration::from_millis($time_ms)).await;
}
}
}
Err(RetryError {retries: errs})
}).await;
r
}
};
}
#[cfg(test)]
mod tests {
use std::{error::Error, fmt::Display, time::Instant, vec};
use super::*;
fn no_args() -> Result<i32, TestError> {
Ok(2)
}
fn one_arg(arg1: i32) -> Result<i32, TestError> {
Ok(arg1)
}
fn three_arg(arg1: i32, arg2: i32, arg3: i32) -> Result<i32, TestError> {
Ok(arg1 + arg2 + arg3)
}
fn one_arg_vec(_v: Vec<i32>) -> Result<Vec<i32>, TestError> {
Err(TestError)
}
#[derive(Debug)]
struct TestError;
impl Display for TestError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Test error")
}
}
impl Error for TestError {}
fn will_fail_no_args() -> Result<(), TestError> {
Err(TestError)
}
fn failing_function(_arg1: i32, _arg2: i32) -> Result<i32, TestError> {
Err(TestError)
}
#[derive(Debug, Clone)]
struct SomeObject {
_v: Vec<i32>,
}
fn will_fail(_some_object: SomeObject, _some_object_2: SomeObject) -> Result<i32, TestError> {
Err(TestError)
}
#[cfg(any(feature = "tokio", feature= "async-std"))]
async fn no_args_async() -> Result<i32, TestError> {
Ok(1)
}
#[cfg(any(feature = "tokio", feature= "async-std"))]
async fn no_args_async_fail() -> Result<i32, TestError> {
Err(TestError)
}
#[cfg(any(feature = "tokio"))]
async fn failing_function_async(_arg1: i32, _arg2: i32) -> Result<i32, TestError> {
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
Err(TestError)
}
#[test]
fn test_will_pass_no_args() {
let actual = retry!(5, no_args);
assert!(actual.is_ok());
}
#[test]
fn test_will_pass_no_args_w_sleep() {
let actual = retry_sleep!(2, 10, no_args);
assert!(actual.is_ok());
}
#[test]
fn test_will_fail_no_args() {
let actual = retry!(5, will_fail_no_args);
assert!(actual.is_err());
assert_eq!(actual.unwrap_err().retries.len(), 5);
}
#[test]
fn test_will_fail_no_args_w_sleep() {
let actual = retry_sleep!(2, 10, will_fail_no_args);
assert!(actual.is_err());
assert_eq!(actual.unwrap_err().retries.len(), 2);
}
#[cfg(feature = "tokio")]
#[tokio::test]
async fn test_will_pass_no_args_async() {
let actual = retry_async!(5, no_args_async);
assert!(actual.is_ok());
}
#[cfg(feature = "tokio")]
#[tokio::test]
async fn test_will_fail_no_args_async() {
let actual = retry_async!(4, no_args_async_fail);
assert!(actual.is_err());
assert_eq!(actual.unwrap_err().retries.len(), 4);
}
#[cfg(feature = "tokio")]
#[tokio::test]
async fn test_pass_function_async_no_args_w_sleep() {
let actual = retry_async_sleep!(2, 10, no_args_async);
assert!(actual.is_ok());
}
#[cfg(feature = "tokio")]
#[tokio::test]
async fn test_fail_function_async_no_args_w_sleep() {
let actual = retry_async_sleep!(2, 10, no_args_async_fail);
assert!(actual.is_err());
assert_eq!(actual.unwrap_err().retries.len(), 2);
}
#[cfg(feature = "async-std")]
#[tokio::test]
async fn test_pass_function_async_no_args_w_sleep() {
let actual = retry_async_sleep!(2, 10, no_args_async);
assert!(actual.is_ok());
}
#[cfg(feature = "async-std")]
#[tokio::test]
async fn test_fail_function_async_no_args_w_sleep() {
let actual = retry_async_sleep!(2, 10, no_args_async_fail);
assert!(actual.is_err());
assert_eq!(actual.unwrap_err().retries.len(), 2);
}
#[test]
fn test_clone_retry() {
let so = SomeObject {
_v: vec![1, 2, 3, 4, 5],
};
let so2 = SomeObject {
_v: vec![1, 2, 3, 4, 5],
};
let actual = retry!(10, will_fail, so, so2);
assert!(actual.is_err());
assert_eq!(actual.unwrap_err().retries.len(), 10);
}
#[test]
fn test_success_function_1_arg() {
let var1 = 5;
let actual = retry!(3, one_arg, var1);
assert!(actual.is_ok());
assert_eq!(actual.unwrap(), 5);
}
#[test]
fn test_success_function_3_arg() {
let var1 = 5;
let var2 = 10;
let var3 = 20;
let actual = retry!(3, three_arg, var1, var2, var3);
assert!(actual.is_ok());
assert_eq!(actual.unwrap(), 35);
}
#[test]
fn test_fail_function() {
let var1 = 1;
let var2 = 2;
let actual = retry!(3, failing_function, var1, var2);
assert!(actual.is_err());
assert_eq!(actual.unwrap_err().retries.len(), 3);
}
#[test]
fn test_vec_clone_fail_function() {
let v = vec![1, 2, 3, 4, 5];
let actual = retry!(5, one_arg_vec, v);
assert!(actual.is_err());
assert_eq!(actual.unwrap_err().retries.len(), 5);
}
#[test]
fn test_fail_function_w_sleep() {
let v = vec![1, 2, 3];
let start_time = Instant::now();
let actual = retry_sleep!(3, 100, one_arg_vec, v);
let elapsed = start_time.elapsed().as_millis();
assert!(elapsed >= 300);
assert!(actual.is_err());
assert_eq!(actual.unwrap_err().retries.len(), 3);
}
#[cfg(feature = "tokio")]
#[tokio::test]
async fn test_fail_function_async() {
let var1 = 1;
let var2 = 2;
let actual = retry_async!(3, failing_function_async, var1, var2);
assert!(actual.is_err());
assert_eq!(actual.unwrap_err().retries.len(), 3);
}
#[cfg(feature = "tokio")]
#[tokio::test]
async fn test_fail_function_async_w_sleep() {
let var1 = 1;
let var2 = 2;
let start_time = Instant::now();
let actual = retry_async_sleep!(2, 100, failing_function_async, var1, var2);
let elapsed = start_time.elapsed().as_millis();
assert!(elapsed >= 200);
assert!(actual.is_err());
assert_eq!(actual.unwrap_err().retries.len(), 2);
}
}