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}