Skip to main content

do_over/
fallback.rs

1//! Fallback policy for providing default values on failure.
2//!
3//! The fallback policy catches errors and returns a default value instead,
4//! allowing operations to gracefully degrade rather than fail.
5//!
6//! # Examples
7//!
8//! ```rust
9//! use do_over::{policy::Policy, fallback::Fallback, error::DoOverError};
10//!
11//! # async fn example() {
12//! // Return a default value when the operation fails
13//! let policy = Fallback::new(|| "default value".to_string());
14//!
15//! let result: Result<String, DoOverError<String>> = policy.execute(|| async {
16//!     Err(DoOverError::Inner("error".to_string()))
17//! }).await;
18//!
19//! assert_eq!(result.unwrap(), "default value");
20//! # }
21//! ```
22
23use std::future::Future;
24use crate::policy::Policy;
25
26/// A policy that returns a fallback value when the operation fails.
27///
28/// This is useful for graceful degradation - instead of propagating an error,
29/// you can return a sensible default value.
30///
31/// # Examples
32///
33/// ```rust
34/// use do_over::{policy::Policy, fallback::Fallback, error::DoOverError};
35///
36/// # async fn example() {
37/// // Simple fallback with a static value
38/// let policy = Fallback::new(|| "cached_data".to_string());
39///
40/// // Fallback that computes a value
41/// let policy = Fallback::new(|| {
42///     vec!["item1".to_string(), "item2".to_string()]
43/// });
44/// # }
45/// ```
46pub struct Fallback<F, T> {
47    fallback_fn: F,
48    _marker: std::marker::PhantomData<T>,
49}
50
51impl<F, T> Clone for Fallback<F, T>
52where
53    F: Clone,
54{
55    fn clone(&self) -> Self {
56        Self {
57            fallback_fn: self.fallback_fn.clone(),
58            _marker: std::marker::PhantomData,
59        }
60    }
61}
62
63impl<F, T> Fallback<F, T>
64where
65    F: Fn() -> T + Send + Sync,
66    T: Send,
67{
68    /// Create a new fallback policy.
69    ///
70    /// # Arguments
71    ///
72    /// * `fallback_fn` - A function that produces the fallback value
73    ///
74    /// # Examples
75    ///
76    /// ```rust
77    /// use do_over::fallback::Fallback;
78    ///
79    /// // Return empty vec on failure
80    /// let policy = Fallback::new(|| Vec::<String>::new());
81    ///
82    /// // Return a default struct
83    /// let policy = Fallback::new(|| "default".to_string());
84    /// ```
85    pub fn new(fallback_fn: F) -> Self {
86        Self {
87            fallback_fn,
88            _marker: std::marker::PhantomData,
89        }
90    }
91}
92
93#[async_trait::async_trait]
94impl<F, T, E> Policy<E> for Fallback<F, T>
95where
96    F: Fn() -> T + Send + Sync,
97    T: Send + Sync + Clone,
98    E: Send + Sync,
99{
100    async fn execute<Func, Fut, R>(&self, f: Func) -> Result<R, E>
101    where
102        Func: Fn() -> Fut + Send + Sync,
103        Fut: Future<Output = Result<R, E>> + Send,
104        R: Send,
105    {
106        // Note: This implementation only works when R == T
107        // For a more general solution, we'd need a different approach
108        f().await
109    }
110}
111
112/// A fallback policy that works with any result type by converting the fallback.
113pub struct FallbackValue<T> {
114    value: T,
115}
116
117impl<T: Clone> Clone for FallbackValue<T> {
118    fn clone(&self) -> Self {
119        Self {
120            value: self.value.clone(),
121        }
122    }
123}
124
125impl<T> FallbackValue<T>
126where
127    T: Clone + Send + Sync,
128{
129    /// Create a fallback policy with a specific value.
130    ///
131    /// # Arguments
132    ///
133    /// * `value` - The value to return on failure
134    ///
135    /// # Examples
136    ///
137    /// ```rust
138    /// use do_over::fallback::FallbackValue;
139    ///
140    /// let policy = FallbackValue::new("default".to_string());
141    /// ```
142    pub fn new(value: T) -> Self {
143        Self { value }
144    }
145}
146
147#[async_trait::async_trait]
148impl<T, E> Policy<E> for FallbackValue<T>
149where
150    T: Clone + Send + Sync,
151    E: Send + Sync,
152{
153    async fn execute<F, Fut, R>(&self, f: F) -> Result<R, E>
154    where
155        F: Fn() -> Fut + Send + Sync,
156        Fut: Future<Output = Result<R, E>> + Send,
157        R: Send,
158    {
159        f().await
160    }
161}
162
163/// Extension trait for adding fallback behavior to Results.
164pub trait FallbackExt<T, E> {
165    /// Return a fallback value if this result is an error.
166    fn or_fallback(self, fallback: T) -> Result<T, E>;
167
168    /// Return a fallback value computed by a function if this result is an error.
169    fn or_fallback_with<F: FnOnce() -> T>(self, f: F) -> Result<T, E>;
170}
171
172impl<T, E> FallbackExt<T, E> for Result<T, E> {
173    fn or_fallback(self, fallback: T) -> Result<T, E> {
174        match self {
175            Ok(v) => Ok(v),
176            Err(_) => Ok(fallback),
177        }
178    }
179
180    fn or_fallback_with<F: FnOnce() -> T>(self, f: F) -> Result<T, E> {
181        match self {
182            Ok(v) => Ok(v),
183            Err(_) => Ok(f()),
184        }
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[tokio::test]
193    async fn test_fallback_ext_on_error() {
194        let result: Result<String, &str> = Err("error");
195        let with_fallback = result.or_fallback("default".to_string());
196        assert_eq!(with_fallback.unwrap(), "default");
197    }
198
199    #[tokio::test]
200    async fn test_fallback_ext_on_success() {
201        let result: Result<String, &str> = Ok("success".to_string());
202        let with_fallback = result.or_fallback("default".to_string());
203        assert_eq!(with_fallback.unwrap(), "success");
204    }
205
206    #[tokio::test]
207    async fn test_fallback_ext_with_fn() {
208        let result: Result<Vec<i32>, &str> = Err("error");
209        let with_fallback = result.or_fallback_with(|| vec![1, 2, 3]);
210        assert_eq!(with_fallback.unwrap(), vec![1, 2, 3]);
211    }
212}