proxima_primitives/pipe/resilience/
fallback.rs1use crate::pipe::primitives::Pipe;
2use core::future::Future;
3
4#[derive(Debug, Clone)]
12pub struct Fallback<P, S> {
13 pub primary: P,
14 pub secondary: S,
15}
16
17impl<P, S> Pipe for Fallback<P, S>
18where
19 P: Pipe,
20 S: Pipe<In = P::In, Out = P::Out, Err = P::Err>,
21 P::In: Clone,
22{
23 type In = P::In;
24 type Out = P::Out;
25 type Err = P::Err;
26
27 fn call(&self, input: Self::In) -> impl Future<Output = Result<Self::Out, Self::Err>> {
28 async move {
29 match self.primary.call(input.clone()).await {
30 Ok(out) => Ok(out),
31 Err(_) => self.secondary.call(input).await,
32 }
33 }
34 }
35}
36
37#[cfg(test)]
38#[allow(clippy::unwrap_used, clippy::expect_used)]
39mod tests {
40 use super::*;
41 use crate::block_on;
42 use core::future::Future;
43
44 #[derive(Debug, Clone, PartialEq)]
45 struct AlwaysFail;
46
47 impl Pipe for AlwaysFail {
48 type In = u32;
49 type Out = u32;
50 type Err = &'static str;
51
52 fn call(&self, _input: u32) -> impl Future<Output = Result<u32, &'static str>> {
53 async { Err("primary failed") }
54 }
55 }
56
57 #[derive(Debug, Clone)]
58 struct AlwaysOk(u32);
59
60 impl Pipe for AlwaysOk {
61 type In = u32;
62 type Out = u32;
63 type Err = &'static str;
64
65 fn call(&self, _input: u32) -> impl Future<Output = Result<u32, &'static str>> {
66 let value = self.0;
67 async move { Ok(value) }
68 }
69 }
70
71 #[derive(Debug, Clone)]
72 struct DoubleInput;
73
74 impl Pipe for DoubleInput {
75 type In = u32;
76 type Out = u32;
77 type Err = &'static str;
78
79 fn call(&self, input: u32) -> impl Future<Output = Result<u32, &'static str>> {
80 async move { Ok(input * 2) }
81 }
82 }
83
84 #[test]
86 fn fallback_uses_primary_when_it_succeeds() {
87 let fallback = Fallback {
88 primary: AlwaysOk(10),
89 secondary: AlwaysFail,
90 };
91 let result = block_on(Pipe::call(&fallback, 0));
92 assert_eq!(result, Ok(10), "primary result returned unchanged");
93 }
94
95 #[test]
96 fn fallback_uses_secondary_when_primary_fails() {
97 let fallback = Fallback {
98 primary: AlwaysFail,
99 secondary: AlwaysOk(42),
100 };
101 let result = block_on(Pipe::call(&fallback, 0));
102 assert_eq!(result, Ok(42), "secondary result after primary error");
103 }
104
105 #[test]
106 fn fallback_passes_original_input_to_secondary() {
107 let fallback = Fallback {
108 primary: AlwaysFail,
109 secondary: DoubleInput,
110 };
111 let result = block_on(Pipe::call(&fallback, 7));
112 assert_eq!(result, Ok(14), "secondary receives original input");
113 }
114
115 #[test]
116 fn fallback_does_not_call_secondary_when_primary_ok() {
117 struct SuccessOrBust(u32);
119 impl Pipe for SuccessOrBust {
120 type In = u32;
121 type Out = u32;
122 type Err = &'static str;
123 fn call(&self, _input: u32) -> impl Future<Output = Result<u32, &'static str>> {
124 let value = self.0;
125 async move { Ok(value) }
126 }
127 }
128 let fallback = Fallback {
129 primary: SuccessOrBust(99),
130 secondary: AlwaysFail,
131 };
132 let result = block_on(Pipe::call(&fallback, 0));
133 assert_eq!(result, Ok(99));
134 }
135}