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}