loadwise_core/strategy/
chain.rs1use super::{SelectionContext, Strategy};
2
3pub struct WithFallback<P, F> {
29 primary: P,
30 fallback: F,
31}
32
33impl<P: std::fmt::Debug, F: std::fmt::Debug> std::fmt::Debug for WithFallback<P, F> {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 f.debug_struct("WithFallback")
36 .field("primary", &self.primary)
37 .field("fallback", &self.fallback)
38 .finish()
39 }
40}
41
42impl<P, F> WithFallback<P, F> {
43 pub fn new(primary: P, fallback: F) -> Self {
44 Self { primary, fallback }
45 }
46}
47
48impl<N, P, F> Strategy<N> for WithFallback<P, F>
49where
50 P: Strategy<N>,
51 F: Strategy<N>,
52{
53 fn select(&self, candidates: &[N], ctx: &SelectionContext) -> Option<usize> {
54 self.primary
55 .select(candidates, ctx)
56 .or_else(|| self.fallback.select(candidates, ctx))
57 }
58}
59
60pub struct FallbackChain<N> {
65 strategies: Vec<Box<dyn Strategy<N> + Send + Sync>>,
66}
67
68impl<N> std::fmt::Debug for FallbackChain<N> {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 f.debug_struct("FallbackChain")
71 .field("strategies", &self.strategies.len())
72 .finish()
73 }
74}
75
76impl<N> FallbackChain<N> {
77 pub fn new(strategies: Vec<Box<dyn Strategy<N> + Send + Sync>>) -> Self {
78 Self { strategies }
79 }
80}
81
82impl<N> Strategy<N> for FallbackChain<N> {
83 fn select(&self, candidates: &[N], ctx: &SelectionContext) -> Option<usize> {
84 self.strategies
85 .iter()
86 .find_map(|s| s.select(candidates, ctx))
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::strategy::RoundRobin;
94
95 #[test]
96 fn with_fallback_propagates_exclude() {
97 let strategy = WithFallback::new(RoundRobin::new(), RoundRobin::new());
98 let nodes = [1, 2];
99 let ctx = SelectionContext::builder().exclude(vec![0, 1]).build();
100 assert_eq!(strategy.select(&nodes, &ctx), None);
102 }
103
104 #[test]
105 fn fallback_chain_propagates_exclude() {
106 let chain: FallbackChain<i32> = FallbackChain::new(vec![
107 Box::new(RoundRobin::new()),
108 Box::new(RoundRobin::new()),
109 ]);
110 let nodes = [1, 2];
111 let ctx = SelectionContext::builder().exclude(vec![0, 1]).build();
112 assert_eq!(chain.select(&nodes, &ctx), None);
113 }
114
115 #[test]
116 fn empty_candidates_returns_none() {
117 let strategy = WithFallback::new(RoundRobin::new(), RoundRobin::new());
118 let nodes: [i32; 0] = [];
119 let ctx = SelectionContext::default();
120 assert_eq!(strategy.select(&nodes, &ctx), None);
121 }
122
123 #[test]
124 fn fallback_used_when_primary_excludes_all() {
125 let strategy = WithFallback::new(RoundRobin::new(), RoundRobin::new());
129 let nodes = [1, 2];
130 let ctx = SelectionContext::builder().exclude(vec![0, 1]).build();
131 assert_eq!(strategy.select(&nodes, &ctx), None);
132 }
133}