Skip to main content

cloud_sdk/
rate_limit.rs

1//! Provider-neutral quota, rate-limit, and retry-delay domains.
2
3mod decision;
4mod extension;
5mod http_date;
6mod quota;
7mod time;
8
9pub use decision::{
10    DelayConflictPolicy, DelayDecision, DelayDecisionError, DelaySource, ExcessDelayPolicy,
11    PastTimestampPolicy, QuotaDelayPolicy, decide_delay,
12};
13pub use extension::{
14    MAX_QUOTA_EXTENSION_NAME_BYTES, MAX_QUOTA_EXTENSION_VALUE_BYTES, QuotaExtension,
15    QuotaExtensionError,
16};
17pub use quota::{
18    MAX_QUOTA_BUCKET_ID_BYTES, MAX_QUOTA_BUCKETS, MAX_QUOTA_EXTENSIONS_PER_BUCKET, QuotaBucket,
19    QuotaBucketId, QuotaBuckets, QuotaError, QuotaReset,
20};
21pub use time::{DelaySeconds, HttpDate, RetryAfter, RetryAfterError, WallClockTimestamp};
22
23/// Rate-limit metadata validation error.
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub enum RateLimitError {
26    /// A reported request limit must be nonzero.
27    LimitZero,
28    /// Remaining requests must not exceed the reported limit.
29    RemainingExceedsLimit,
30}
31
32impl_static_error!(RateLimitError,
33    Self::LimitZero => "rate limit must be nonzero",
34    Self::RemainingExceedsLimit => "remaining requests exceed the rate limit",
35);
36
37/// Legacy single-bucket rate-limit metadata.
38///
39/// New provider decoders should expose `QuotaBuckets` and use this type only
40/// as a compatibility view for APIs that publish one absolute-reset bucket.
41#[derive(Clone, Copy, Debug, Eq, PartialEq)]
42pub struct RateLimit {
43    limit: u64,
44    remaining: u64,
45    reset_epoch_seconds: u64,
46}
47
48impl RateLimit {
49    /// Creates coherent rate-limit metadata.
50    pub const fn new(
51        limit: u64,
52        remaining: u64,
53        reset_epoch_seconds: u64,
54    ) -> Result<Self, RateLimitError> {
55        if limit == 0 {
56            return Err(RateLimitError::LimitZero);
57        }
58        if remaining > limit {
59            return Err(RateLimitError::RemainingExceedsLimit);
60        }
61        Ok(Self {
62            limit,
63            remaining,
64            reset_epoch_seconds,
65        })
66    }
67
68    /// Returns the total request limit for the provider's time frame.
69    #[must_use]
70    pub const fn limit(self) -> u64 {
71        self.limit
72    }
73
74    /// Returns the number of requests remaining in the current time frame.
75    #[must_use]
76    pub const fn remaining(self) -> u64 {
77        self.remaining
78    }
79
80    /// Returns the provider reset time as Unix epoch seconds.
81    #[must_use]
82    pub const fn reset_epoch_seconds(self) -> u64 {
83        self.reset_epoch_seconds
84    }
85}
86
87#[cfg(test)]
88mod legacy_tests {
89    use super::{RateLimit, RateLimitError};
90
91    #[test]
92    fn rejects_incoherent_metadata() {
93        assert_eq!(RateLimit::new(0, 0, 42), Err(RateLimitError::LimitZero));
94        assert_eq!(
95            RateLimit::new(10, 11, 42),
96            Err(RateLimitError::RemainingExceedsLimit)
97        );
98    }
99
100    #[test]
101    fn exposes_coherent_metadata() {
102        let rate_limit = RateLimit::new(3600, 3599, 42);
103        assert!(rate_limit.is_ok());
104        let Ok(rate_limit) = rate_limit else { return };
105        assert_eq!(rate_limit.limit(), 3600);
106        assert_eq!(rate_limit.remaining(), 3599);
107        assert_eq!(rate_limit.reset_epoch_seconds(), 42);
108    }
109}
110
111#[cfg(test)]
112mod tests;