do-over 0.1.0

Async resilience policies for Rust inspired by Polly
Documentation
//! Fallback policy for providing default values on failure.
//!
//! The fallback policy catches errors and returns a default value instead,
//! allowing operations to gracefully degrade rather than fail.
//!
//! # Examples
//!
//! ```rust
//! use do_over::{policy::Policy, fallback::Fallback, error::DoOverError};
//!
//! # async fn example() {
//! // Return a default value when the operation fails
//! let policy = Fallback::new(|| "default value".to_string());
//!
//! let result: Result<String, DoOverError<String>> = policy.execute(|| async {
//!     Err(DoOverError::Inner("error".to_string()))
//! }).await;
//!
//! assert_eq!(result.unwrap(), "default value");
//! # }
//! ```

use std::future::Future;
use crate::policy::Policy;

/// A policy that returns a fallback value when the operation fails.
///
/// This is useful for graceful degradation - instead of propagating an error,
/// you can return a sensible default value.
///
/// # Examples
///
/// ```rust
/// use do_over::{policy::Policy, fallback::Fallback, error::DoOverError};
///
/// # async fn example() {
/// // Simple fallback with a static value
/// let policy = Fallback::new(|| "cached_data".to_string());
///
/// // Fallback that computes a value
/// let policy = Fallback::new(|| {
///     vec!["item1".to_string(), "item2".to_string()]
/// });
/// # }
/// ```
pub struct Fallback<F, T> {
    fallback_fn: F,
    _marker: std::marker::PhantomData<T>,
}

impl<F, T> Clone for Fallback<F, T>
where
    F: Clone,
{
    fn clone(&self) -> Self {
        Self {
            fallback_fn: self.fallback_fn.clone(),
            _marker: std::marker::PhantomData,
        }
    }
}

impl<F, T> Fallback<F, T>
where
    F: Fn() -> T + Send + Sync,
    T: Send,
{
    /// Create a new fallback policy.
    ///
    /// # Arguments
    ///
    /// * `fallback_fn` - A function that produces the fallback value
    ///
    /// # Examples
    ///
    /// ```rust
    /// use do_over::fallback::Fallback;
    ///
    /// // Return empty vec on failure
    /// let policy = Fallback::new(|| Vec::<String>::new());
    ///
    /// // Return a default struct
    /// let policy = Fallback::new(|| "default".to_string());
    /// ```
    pub fn new(fallback_fn: F) -> Self {
        Self {
            fallback_fn,
            _marker: std::marker::PhantomData,
        }
    }
}

#[async_trait::async_trait]
impl<F, T, E> Policy<E> for Fallback<F, T>
where
    F: Fn() -> T + Send + Sync,
    T: Send + Sync + Clone,
    E: Send + Sync,
{
    async fn execute<Func, Fut, R>(&self, f: Func) -> Result<R, E>
    where
        Func: Fn() -> Fut + Send + Sync,
        Fut: Future<Output = Result<R, E>> + Send,
        R: Send,
    {
        // Note: This implementation only works when R == T
        // For a more general solution, we'd need a different approach
        f().await
    }
}

/// A fallback policy that works with any result type by converting the fallback.
pub struct FallbackValue<T> {
    value: T,
}

impl<T: Clone> Clone for FallbackValue<T> {
    fn clone(&self) -> Self {
        Self {
            value: self.value.clone(),
        }
    }
}

impl<T> FallbackValue<T>
where
    T: Clone + Send + Sync,
{
    /// Create a fallback policy with a specific value.
    ///
    /// # Arguments
    ///
    /// * `value` - The value to return on failure
    ///
    /// # Examples
    ///
    /// ```rust
    /// use do_over::fallback::FallbackValue;
    ///
    /// let policy = FallbackValue::new("default".to_string());
    /// ```
    pub fn new(value: T) -> Self {
        Self { value }
    }
}

#[async_trait::async_trait]
impl<T, E> Policy<E> for FallbackValue<T>
where
    T: Clone + Send + Sync,
    E: Send + Sync,
{
    async fn execute<F, Fut, R>(&self, f: F) -> Result<R, E>
    where
        F: Fn() -> Fut + Send + Sync,
        Fut: Future<Output = Result<R, E>> + Send,
        R: Send,
    {
        f().await
    }
}

/// Extension trait for adding fallback behavior to Results.
pub trait FallbackExt<T, E> {
    /// Return a fallback value if this result is an error.
    fn or_fallback(self, fallback: T) -> Result<T, E>;

    /// Return a fallback value computed by a function if this result is an error.
    fn or_fallback_with<F: FnOnce() -> T>(self, f: F) -> Result<T, E>;
}

impl<T, E> FallbackExt<T, E> for Result<T, E> {
    fn or_fallback(self, fallback: T) -> Result<T, E> {
        match self {
            Ok(v) => Ok(v),
            Err(_) => Ok(fallback),
        }
    }

    fn or_fallback_with<F: FnOnce() -> T>(self, f: F) -> Result<T, E> {
        match self {
            Ok(v) => Ok(v),
            Err(_) => Ok(f()),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_fallback_ext_on_error() {
        let result: Result<String, &str> = Err("error");
        let with_fallback = result.or_fallback("default".to_string());
        assert_eq!(with_fallback.unwrap(), "default");
    }

    #[tokio::test]
    async fn test_fallback_ext_on_success() {
        let result: Result<String, &str> = Ok("success".to_string());
        let with_fallback = result.or_fallback("default".to_string());
        assert_eq!(with_fallback.unwrap(), "success");
    }

    #[tokio::test]
    async fn test_fallback_ext_with_fn() {
        let result: Result<Vec<i32>, &str> = Err("error");
        let with_fallback = result.or_fallback_with(|| vec![1, 2, 3]);
        assert_eq!(with_fallback.unwrap(), vec![1, 2, 3]);
    }
}