unwrap-retry 0.3.0

Easily auto retrying your Results and Options
Documentation
#![cfg_attr(
    feature = "track-caller",
    feature(async_fn_track_caller)
)]
#![allow(async_fn_in_trait)]

use std::{error::Error, future::Future, panic::Location, thread::sleep, time::Duration};

const INTERVAL_MS: Duration = Duration::from_millis(50);

pub trait RetryableResultFn<T> {
    fn unwrap_blocking(self) -> T;
}

impl<T, E: Error, F: FnMut() -> Result<T, E>> RetryableResultFn<T> for F {
    #[cfg_attr(
        feature = "track-caller",
        track_caller
    )]
    fn unwrap_blocking(mut self) -> T {
        let caller = Location::caller();
        let mut res = self();
        let mut err = None;

        loop {
            match res {
                Ok(o) => return o,
                Err(ref e) => {
                    let e = format!("{e:#?}");
                    if err.as_ref() != Some(&e) {
                        if cfg!(feature = "track-caller") {
                            println!(
                                "Error at {}:{}:{}: {e}, will block till success...",
                                caller.file(),
                                caller.line(),
                                caller.column()
                            );
                        } else {
                            println!("Error: {e}, will block till success...");
                        }
                        err = Some(e);
                    }
                    res = self();
                }
            }
        }
    }
}

pub trait RetryableResultAsyncFn<T> {
    async fn unwrap_res(self, wait: Option<Duration>) -> T;
}

impl<T, E: Error, Fut: Future<Output = Result<T, E>>, F: FnMut() -> Fut> RetryableResultAsyncFn<T> for F {
    #[cfg_attr(
        feature = "track-caller",
        track_caller
    )]
    async fn unwrap_res(mut self, wait: Option<Duration>) -> T {
        let caller = Location::caller();
        let mut res = self().await;
        let mut err: Option<String> = None;

        loop {
            match res {
                Ok(o) => return o,
                Err(ref e) => {
                    let e = format!("{e:#?}");
                    if err.as_ref() != Some(&e) {
                        if cfg!(feature = "track-caller") {
                            println!(
                                "Error at {}:{}:{}: {e}, will block till success...",
                                caller.file(),
                                caller.line(),
                                caller.column()
                            );
                        } else {
                            println!("Error: {e}, will block till success...");
                        }
                        err = Some(e);
                    }
                    res = self().await;
                }
            }
            sleep(wait.unwrap_or(INTERVAL_MS));
        }
    }
}

pub trait RetryableOptionFn<T> {
    fn unwrap_blocking(self) -> T;
}

impl<T, F: FnMut() -> Option<T>> RetryableOptionFn<T> for F {
    #[cfg_attr(
        feature = "track-caller",
        track_caller
    )]
    fn unwrap_blocking(mut self) -> T {
        let caller = Location::caller();
        let mut printed = false;

        loop {
            match self() {
                Some(v) => return v,
                None => {
                    if !printed {
                        if cfg!(feature = "track-caller") {
                            println!(
                                "None at {}:{}:{}, will block till Some...",
                                caller.file(),
                                caller.line(),
                                caller.column()
                            );
                        } else {
                            println!("None, will block till Some...");
                        }
                        printed = true;
                    }
                }
            }
        }
    }
}

pub trait RetryableOptionAsyncFn<T> {
    async fn unwrap_opt(self, wait: Option<Duration>) -> T;
}

impl<T, Fut: Future<Output = Option<T>>, F: FnMut() -> Fut> RetryableOptionAsyncFn<T> for F {
    #[cfg_attr(
        feature = "track-caller",
        track_caller
    )]
    async fn unwrap_opt(mut self, wait: Option<Duration>) -> T {
        let caller = Location::caller();
        let mut printed = false;

        loop {
            match self().await {
                Some(v) => return v,
                None => {
                    if !printed {
                        if cfg!(feature = "track-caller") {
                            println!(
                                "None at {}:{}:{}, will block till Some...",
                                caller.file(),
                                caller.line(),
                                caller.column()
                            );
                        } else {
                            println!("None, will block till Some...");
                        }
                        printed = true;
                    }
                }
            }
            sleep(wait.unwrap_or(INTERVAL_MS));
        }
    }
}