Skip to main content

cognis_core/wrappers/
fallback.rs

1//! Fallback wrapper — try `primary`, switch to `fallback` on error.
2
3use std::marker::PhantomData;
4
5use async_trait::async_trait;
6
7use crate::runnable::{Runnable, RunnableConfig};
8use crate::Result;
9
10/// Try the primary runnable; on any error, fall through to the fallback.
11///
12/// Chain multiple fallbacks by repeated wrapping.
13pub struct Fallback<P, F, I, O> {
14    primary: P,
15    fallback: F,
16    _phantom: PhantomData<fn(I) -> O>,
17}
18
19impl<P, F, I, O> Fallback<P, F, I, O>
20where
21    P: Runnable<I, O>,
22    F: Runnable<I, O>,
23    I: Clone + Send + 'static,
24    O: Send + 'static,
25{
26    /// Build a fallback wrapper.
27    pub fn new(primary: P, fallback: F) -> Self {
28        Self {
29            primary,
30            fallback,
31            _phantom: PhantomData,
32        }
33    }
34}
35
36#[async_trait]
37impl<P, F, I, O> Runnable<I, O> for Fallback<P, F, I, O>
38where
39    P: Runnable<I, O>,
40    F: Runnable<I, O>,
41    I: Clone + Send + 'static,
42    O: Send + 'static,
43{
44    async fn invoke(&self, input: I, config: RunnableConfig) -> Result<O> {
45        match self.primary.invoke(input.clone(), config.clone()).await {
46            Ok(v) => Ok(v),
47            Err(_) => self.fallback.invoke(input, config).await,
48        }
49    }
50    fn name(&self) -> &str {
51        "Fallback"
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use crate::CognisError;
59
60    struct Err1;
61    struct Ok99;
62
63    #[async_trait]
64    impl Runnable<u32, u32> for Err1 {
65        async fn invoke(&self, _: u32, _: RunnableConfig) -> Result<u32> {
66            Err(CognisError::Internal("nope".into()))
67        }
68    }
69
70    #[async_trait]
71    impl Runnable<u32, u32> for Ok99 {
72        async fn invoke(&self, _: u32, _: RunnableConfig) -> Result<u32> {
73            Ok(99)
74        }
75    }
76
77    #[tokio::test]
78    async fn falls_through_on_error() {
79        let f = Fallback::new(Err1, Ok99);
80        assert_eq!(f.invoke(0, RunnableConfig::default()).await.unwrap(), 99);
81    }
82
83    #[tokio::test]
84    async fn primary_wins_on_success() {
85        let f = Fallback::new(Ok99, Err1);
86        assert_eq!(f.invoke(0, RunnableConfig::default()).await.unwrap(), 99);
87    }
88}