use crate::pipe::primitives::Pipe;
use core::future::Future;
#[derive(Debug, Clone)]
pub struct Fallback<P, S> {
pub primary: P,
pub secondary: S,
}
impl<P, S> Pipe for Fallback<P, S>
where
P: Pipe,
S: Pipe<In = P::In, Out = P::Out, Err = P::Err>,
P::In: Clone,
{
type In = P::In;
type Out = P::Out;
type Err = P::Err;
fn call(&self, input: Self::In) -> impl Future<Output = Result<Self::Out, Self::Err>> {
async move {
match self.primary.call(input.clone()).await {
Ok(out) => Ok(out),
Err(_) => self.secondary.call(input).await,
}
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::block_on;
use core::future::Future;
#[derive(Debug, Clone, PartialEq)]
struct AlwaysFail;
impl Pipe for AlwaysFail {
type In = u32;
type Out = u32;
type Err = &'static str;
fn call(&self, _input: u32) -> impl Future<Output = Result<u32, &'static str>> {
async { Err("primary failed") }
}
}
#[derive(Debug, Clone)]
struct AlwaysOk(u32);
impl Pipe for AlwaysOk {
type In = u32;
type Out = u32;
type Err = &'static str;
fn call(&self, _input: u32) -> impl Future<Output = Result<u32, &'static str>> {
let value = self.0;
async move { Ok(value) }
}
}
#[derive(Debug, Clone)]
struct DoubleInput;
impl Pipe for DoubleInput {
type In = u32;
type Out = u32;
type Err = &'static str;
fn call(&self, input: u32) -> impl Future<Output = Result<u32, &'static str>> {
async move { Ok(input * 2) }
}
}
#[test]
fn fallback_uses_primary_when_it_succeeds() {
let fallback = Fallback {
primary: AlwaysOk(10),
secondary: AlwaysFail,
};
let result = block_on(Pipe::call(&fallback, 0));
assert_eq!(result, Ok(10), "primary result returned unchanged");
}
#[test]
fn fallback_uses_secondary_when_primary_fails() {
let fallback = Fallback {
primary: AlwaysFail,
secondary: AlwaysOk(42),
};
let result = block_on(Pipe::call(&fallback, 0));
assert_eq!(result, Ok(42), "secondary result after primary error");
}
#[test]
fn fallback_passes_original_input_to_secondary() {
let fallback = Fallback {
primary: AlwaysFail,
secondary: DoubleInput,
};
let result = block_on(Pipe::call(&fallback, 7));
assert_eq!(result, Ok(14), "secondary receives original input");
}
#[test]
fn fallback_does_not_call_secondary_when_primary_ok() {
struct SuccessOrBust(u32);
impl Pipe for SuccessOrBust {
type In = u32;
type Out = u32;
type Err = &'static str;
fn call(&self, _input: u32) -> impl Future<Output = Result<u32, &'static str>> {
let value = self.0;
async move { Ok(value) }
}
}
let fallback = Fallback {
primary: SuccessOrBust(99),
secondary: AlwaysFail,
};
let result = block_on(Pipe::call(&fallback, 0));
assert_eq!(result, Ok(99));
}
}