ranked_semaphore/
config.rs

1/// Queue strategy for waiters at a given priority level.
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3pub enum QueueStrategy {
4    /// First In, First Out - waiters are served in the order they arrive.
5    Fifo,
6    /// Last In, First Out - waiters are served in reverse order of arrival.
7    Lifo,
8}
9
10/// A rule for determining queue strategy based on priority.
11#[derive(Debug, Clone)]
12pub(crate) enum PriorityRule {
13    /// Default strategy for all priorities not matched by other rules.
14    Default(QueueStrategy),
15    /// Strategy for an exact priority value.
16    Exact(isize, QueueStrategy),
17    /// Strategy for priorities within a range [min, max] (inclusive).
18    /// Use Range(x, isize::MAX) for greater-or-equal behavior.
19    /// Use Range(isize::MIN, x) for less-or-equal behavior.
20    Range(isize, isize, QueueStrategy),
21}
22
23/// Configuration for priority-based queue strategies.
24#[derive(Debug, Clone)]
25pub struct PriorityConfig {
26    pub(crate) rules: Vec<PriorityRule>,
27}
28
29impl PriorityConfig {
30    /// Create a new empty priority configuration.
31    pub fn new() -> Self {
32        Self { rules: Vec::new() }
33    }
34
35    /// Set the default queue strategy for all priorities not matched by other rules.
36    pub fn default_strategy(mut self, strategy: QueueStrategy) -> Self {
37        self.rules.push(PriorityRule::Default(strategy));
38        self
39    }
40
41    /// Set the queue strategy for an exact priority value.
42    pub fn exact(mut self, priority: isize, strategy: QueueStrategy) -> Self {
43        self.rules.push(PriorityRule::Exact(priority, strategy));
44        self
45    }
46
47    /// Set the queue strategy for priorities greater than or equal to the threshold.
48    /// This is a convenience method that creates a range [threshold, isize::MAX].
49    pub fn greater_or_equal(mut self, threshold: isize, strategy: QueueStrategy) -> Self {
50        self.rules
51            .push(PriorityRule::Range(threshold, isize::MAX, strategy));
52        self
53    }
54
55    /// Set the queue strategy for priorities greater than the threshold (exclusive).
56    /// This is a convenience method that creates a range [threshold + 1, isize::MAX].
57    pub fn greater_than(mut self, threshold: isize, strategy: QueueStrategy) -> Self {
58        let min = threshold.saturating_add(1);
59        self.rules
60            .push(PriorityRule::Range(min, isize::MAX, strategy));
61        self
62    }
63
64    /// Set the queue strategy for priorities less than or equal to the threshold.
65    /// This is a convenience method that creates a range [isize::MIN, threshold].
66    pub fn less_or_equal(mut self, threshold: isize, strategy: QueueStrategy) -> Self {
67        self.rules
68            .push(PriorityRule::Range(isize::MIN, threshold, strategy));
69        self
70    }
71
72    /// Set the queue strategy for priorities less than the threshold (exclusive).
73    /// This is a convenience method that creates a range [isize::MIN, threshold - 1].
74    pub fn less_than(mut self, threshold: isize, strategy: QueueStrategy) -> Self {
75        let max = threshold.saturating_sub(1);
76        self.rules
77            .push(PriorityRule::Range(isize::MIN, max, strategy));
78        self
79    }
80
81    /// Set the queue strategy for priorities within a range [min, max] (inclusive).
82    pub fn range(mut self, min: isize, max: isize, strategy: QueueStrategy) -> Self {
83        self.rules.push(PriorityRule::Range(min, max, strategy));
84        self
85    }
86
87    /// Resolve the queue strategy for a given priority.
88    ///
89    /// Rules are evaluated in the following order:
90    /// 1. Exact matches (highest priority)
91    /// 2. Range matches (first match wins)
92    /// 3. Default strategy
93    pub(crate) fn resolve_strategy(&self, priority: isize) -> QueueStrategy {
94        let mut default_strategy = QueueStrategy::Fifo;
95
96        // First pass: Check for exact matches
97        for rule in &self.rules {
98            match rule {
99                PriorityRule::Exact(p, strategy) if *p == priority => return *strategy,
100                PriorityRule::Default(strategy) => default_strategy = *strategy,
101                _ => {}
102            }
103        }
104
105        // Second pass: Check for range matches
106        for rule in &self.rules {
107            match rule {
108                PriorityRule::Range(min, max, strategy) if priority >= *min && priority <= *max => {
109                    return *strategy
110                }
111                _ => {}
112            }
113        }
114
115        default_strategy
116    }
117
118    /// Returns the queue strategy that would be used for the given priority.
119    /// 
120    /// This method evaluates all configured rules to determine which queue strategy
121    /// (FIFO or LIFO) would be applied to a waiter with the specified priority.
122    ///
123    /// # Arguments
124    ///
125    /// * `priority` - The priority level to query
126    ///
127    /// # Returns
128    ///
129    /// The [`QueueStrategy`] that would be used for the given priority.
130    ///
131    /// # Examples
132    ///
133    /// ```rust
134    /// use ranked_semaphore::{PriorityConfig, QueueStrategy};
135    ///
136    /// let config = PriorityConfig::new()
137    ///     .default_strategy(QueueStrategy::Fifo)
138    ///     .exact(10, QueueStrategy::Lifo);
139    ///
140    /// assert_eq!(config.strategy_for_priority(5), QueueStrategy::Fifo);
141    /// assert_eq!(config.strategy_for_priority(10), QueueStrategy::Lifo);
142    /// ```
143    pub fn strategy_for_priority(&self, priority: isize) -> QueueStrategy {
144        self.resolve_strategy(priority)
145    }
146}
147
148impl Default for PriorityConfig {
149    fn default() -> Self {
150        Self::new().default_strategy(QueueStrategy::Fifo)
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_exact_match() {
160        let config = PriorityConfig::new()
161            .default_strategy(QueueStrategy::Fifo)
162            .exact(5, QueueStrategy::Lifo);
163
164        assert_eq!(config.resolve_strategy(5), QueueStrategy::Lifo);
165        assert_eq!(config.resolve_strategy(4), QueueStrategy::Fifo);
166        assert_eq!(config.resolve_strategy(6), QueueStrategy::Fifo);
167    }
168
169    #[test]
170    fn test_range_match() {
171        let config = PriorityConfig::new()
172            .default_strategy(QueueStrategy::Fifo)
173            .range(1, 10, QueueStrategy::Lifo);
174
175        assert_eq!(config.resolve_strategy(0), QueueStrategy::Fifo);
176        assert_eq!(config.resolve_strategy(1), QueueStrategy::Lifo);
177        assert_eq!(config.resolve_strategy(5), QueueStrategy::Lifo);
178        assert_eq!(config.resolve_strategy(10), QueueStrategy::Lifo);
179        assert_eq!(config.resolve_strategy(11), QueueStrategy::Fifo);
180    }
181
182    #[test]
183    fn test_greater_or_equal() {
184        let config = PriorityConfig::new()
185            .default_strategy(QueueStrategy::Fifo)
186            .greater_or_equal(5, QueueStrategy::Lifo);
187
188        assert_eq!(config.resolve_strategy(4), QueueStrategy::Fifo);
189        assert_eq!(config.resolve_strategy(5), QueueStrategy::Lifo);
190        assert_eq!(config.resolve_strategy(10), QueueStrategy::Lifo);
191    }
192
193    #[test]
194    fn test_greater_than() {
195        let config = PriorityConfig::new()
196            .default_strategy(QueueStrategy::Fifo)
197            .greater_than(5, QueueStrategy::Lifo);
198
199        assert_eq!(config.resolve_strategy(5), QueueStrategy::Fifo);
200        assert_eq!(config.resolve_strategy(6), QueueStrategy::Lifo);
201        assert_eq!(config.resolve_strategy(10), QueueStrategy::Lifo);
202    }
203
204    #[test]
205    fn test_less_or_equal() {
206        let config = PriorityConfig::new()
207            .default_strategy(QueueStrategy::Lifo)
208            .less_or_equal(0, QueueStrategy::Fifo);
209
210        assert_eq!(config.resolve_strategy(-5), QueueStrategy::Fifo);
211        assert_eq!(config.resolve_strategy(0), QueueStrategy::Fifo);
212        assert_eq!(config.resolve_strategy(1), QueueStrategy::Lifo);
213    }
214
215    #[test]
216    fn test_less_than() {
217        let config = PriorityConfig::new()
218            .default_strategy(QueueStrategy::Fifo)
219            .less_than(0, QueueStrategy::Lifo);
220
221        assert_eq!(config.resolve_strategy(-5), QueueStrategy::Lifo);
222        assert_eq!(config.resolve_strategy(0), QueueStrategy::Fifo);
223        assert_eq!(config.resolve_strategy(1), QueueStrategy::Fifo);
224    }
225
226    #[test]
227    fn test_priority_order() {
228        let config = PriorityConfig::new()
229            .default_strategy(QueueStrategy::Fifo)
230            .greater_or_equal(5, QueueStrategy::Lifo)
231            .exact(10, QueueStrategy::Fifo); // Exact should override range
232
233        assert_eq!(config.resolve_strategy(5), QueueStrategy::Lifo);
234        assert_eq!(config.resolve_strategy(8), QueueStrategy::Lifo);
235        assert_eq!(config.resolve_strategy(10), QueueStrategy::Fifo); // Exact wins
236    }
237
238    #[test]
239    fn test_multiple_thresholds() {
240        let config = PriorityConfig::new()
241            .default_strategy(QueueStrategy::Fifo)
242            .range(5, isize::MAX, QueueStrategy::Lifo)
243            .range(10, isize::MAX, QueueStrategy::Fifo); // More specific range should win
244
245        assert_eq!(config.resolve_strategy(4), QueueStrategy::Fifo);
246        // First range [5, MAX] matches, so LIFO (first match wins)
247        assert_eq!(config.resolve_strategy(7), QueueStrategy::Lifo);
248        assert_eq!(config.resolve_strategy(15), QueueStrategy::Lifo);
249    }
250}