Skip to main content

throttle_net/
decision.rs

1//! The outcome of a non-blocking acquisition attempt.
2
3use core::time::Duration;
4
5/// What happened when a limiter was asked for tokens without waiting.
6///
7/// This is the synchronous core that the waiting
8/// [`acquire`](crate::Throttle::acquire) surface is built on, and the value the
9/// [`Limiter`](crate::Limiter) trait returns so composite limiters can reason
10/// about an outcome before deciding whether to wait. The waiting layer maps it
11/// to either a return, a sleep, or a [`ThrottleError`](crate::ThrottleError).
12///
13/// `#[non_exhaustive]`: later phases add outcomes (for example, a deadline or a
14/// circuit-open signal), so a `match` on it must include a wildcard arm.
15///
16/// # Examples
17///
18/// ```
19/// use throttle_net::{Decision, Limiter, Throttle};
20///
21/// let throttle = Throttle::per_second(1);
22/// // The bucket starts full, so the first unit is granted immediately.
23/// assert_eq!(throttle.acquire_cost(1), Decision::Acquired);
24/// // The next unit must wait for the bucket to refill.
25/// assert!(matches!(throttle.acquire_cost(1), Decision::Retry { .. }));
26/// ```
27#[non_exhaustive]
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum Decision {
30    /// The tokens were granted and have been deducted.
31    Acquired,
32    /// The request was refused for now. The bucket will hold enough tokens
33    /// again after `after` has elapsed, assuming no competing acquisitions.
34    Retry {
35        /// The minimum wait until a retry of the same cost can succeed.
36        after: Duration,
37    },
38    /// The request can never succeed: the cost exceeds the limiter's capacity,
39    /// so no amount of waiting will satisfy it.
40    Impossible,
41}
42
43impl Decision {
44    /// Returns `true` if the tokens were granted.
45    ///
46    /// # Examples
47    ///
48    /// ```
49    /// use throttle_net::Decision;
50    ///
51    /// assert!(Decision::Acquired.is_acquired());
52    /// assert!(!Decision::Impossible.is_acquired());
53    /// ```
54    #[inline]
55    #[must_use]
56    pub const fn is_acquired(&self) -> bool {
57        matches!(self, Self::Acquired)
58    }
59
60    /// Returns the wait before a retry can succeed, or `None` when the request
61    /// was granted or is impossible.
62    ///
63    /// # Examples
64    ///
65    /// ```
66    /// use std::time::Duration;
67    /// use throttle_net::Decision;
68    ///
69    /// let d = Decision::Retry { after: Duration::from_millis(20) };
70    /// assert_eq!(d.retry_after(), Some(Duration::from_millis(20)));
71    /// assert_eq!(Decision::Acquired.retry_after(), None);
72    /// ```
73    #[inline]
74    #[must_use]
75    pub const fn retry_after(&self) -> Option<Duration> {
76        match self {
77            Self::Retry { after } => Some(*after),
78            _ => None,
79        }
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::Decision;
86    use core::time::Duration;
87
88    #[test]
89    fn test_is_acquired_only_for_acquired() {
90        assert!(Decision::Acquired.is_acquired());
91        assert!(
92            !Decision::Retry {
93                after: Duration::ZERO
94            }
95            .is_acquired()
96        );
97        assert!(!Decision::Impossible.is_acquired());
98    }
99
100    #[test]
101    fn test_retry_after_returns_wait_only_for_retry() {
102        let wait = Duration::from_millis(5);
103        assert_eq!(Decision::Retry { after: wait }.retry_after(), Some(wait));
104        assert_eq!(Decision::Acquired.retry_after(), None);
105        assert_eq!(Decision::Impossible.retry_after(), None);
106    }
107}