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 std::time::Duration;
20/// use throttle_net::Decision;
21///
22/// // Granted now:
23/// assert!(Decision::Acquired.is_acquired());
24/// // Refused for now, retry after the wait:
25/// let d = Decision::Retry { after: Duration::from_millis(20) };
26/// assert_eq!(d.retry_after(), Some(Duration::from_millis(20)));
27/// // Can never succeed (cost exceeds capacity):
28/// assert!(!Decision::Impossible.is_acquired());
29/// ```
30#[non_exhaustive]
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum Decision {
33    /// The tokens were granted and have been deducted.
34    Acquired,
35    /// The request was refused for now. The bucket will hold enough tokens
36    /// again after `after` has elapsed, assuming no competing acquisitions.
37    Retry {
38        /// The minimum wait until a retry of the same cost can succeed.
39        after: Duration,
40    },
41    /// The request can never succeed: the cost exceeds the limiter's capacity,
42    /// so no amount of waiting will satisfy it.
43    Impossible,
44}
45
46impl Decision {
47    /// Returns `true` if the tokens were granted.
48    ///
49    /// # Examples
50    ///
51    /// ```
52    /// use throttle_net::Decision;
53    ///
54    /// assert!(Decision::Acquired.is_acquired());
55    /// assert!(!Decision::Impossible.is_acquired());
56    /// ```
57    #[inline]
58    #[must_use]
59    pub const fn is_acquired(&self) -> bool {
60        matches!(self, Self::Acquired)
61    }
62
63    /// Returns the wait before a retry can succeed, or `None` when the request
64    /// was granted or is impossible.
65    ///
66    /// # Examples
67    ///
68    /// ```
69    /// use std::time::Duration;
70    /// use throttle_net::Decision;
71    ///
72    /// let d = Decision::Retry { after: Duration::from_millis(20) };
73    /// assert_eq!(d.retry_after(), Some(Duration::from_millis(20)));
74    /// assert_eq!(Decision::Acquired.retry_after(), None);
75    /// ```
76    #[inline]
77    #[must_use]
78    pub const fn retry_after(&self) -> Option<Duration> {
79        match self {
80            Self::Retry { after } => Some(*after),
81            _ => None,
82        }
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::Decision;
89    use core::time::Duration;
90
91    #[test]
92    fn test_is_acquired_only_for_acquired() {
93        assert!(Decision::Acquired.is_acquired());
94        assert!(
95            !Decision::Retry {
96                after: Duration::ZERO
97            }
98            .is_acquired()
99        );
100        assert!(!Decision::Impossible.is_acquired());
101    }
102
103    #[test]
104    fn test_retry_after_returns_wait_only_for_retry() {
105        let wait = Duration::from_millis(5);
106        assert_eq!(Decision::Retry { after: wait }.retry_after(), Some(wait));
107        assert_eq!(Decision::Acquired.retry_after(), None);
108        assert_eq!(Decision::Impossible.retry_after(), None);
109    }
110}