Skip to main content

cloud_sdk/rate_limit/
time.rs

1use super::http_date::parse_http_date;
2
3/// A relative delay measured in whole seconds.
4#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
5pub struct DelaySeconds(u64);
6
7impl DelaySeconds {
8    /// Creates a relative delay.
9    #[must_use]
10    pub const fn new(seconds: u64) -> Self {
11        Self(seconds)
12    }
13
14    /// Returns the delay in whole seconds.
15    #[must_use]
16    pub const fn get(self) -> u64 {
17        self.0
18    }
19}
20
21/// An absolute Unix wall-clock timestamp in whole seconds.
22#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
23pub struct WallClockTimestamp(u64);
24
25impl WallClockTimestamp {
26    /// Creates an absolute Unix timestamp.
27    #[must_use]
28    pub const fn new(epoch_seconds: u64) -> Self {
29        Self(epoch_seconds)
30    }
31
32    /// Returns whole seconds since the Unix epoch.
33    #[must_use]
34    pub const fn get(self) -> u64 {
35        self.0
36    }
37}
38
39/// A parsed HTTP date represented as signed Unix seconds.
40#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
41pub struct HttpDate(i64);
42
43impl HttpDate {
44    pub(crate) const fn new(epoch_seconds: i64) -> Self {
45        Self(epoch_seconds)
46    }
47
48    /// Returns signed whole seconds relative to the Unix epoch.
49    #[must_use]
50    pub const fn epoch_seconds(self) -> i64 {
51        self.0
52    }
53}
54
55/// Standard HTTP `Retry-After` value.
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57pub enum RetryAfter {
58    /// Relative delay-seconds form.
59    Delay(DelaySeconds),
60    /// Absolute HTTP-date form.
61    HttpDate(HttpDate),
62}
63
64impl RetryAfter {
65    /// Parses delay-seconds or any HTTP-date form required by RFC 9110.
66    ///
67    /// `now` is used only to resolve the two-digit year in the obsolete
68    /// RFC 850 form. The function acquires no clock itself.
69    pub fn parse(value: &[u8], now: WallClockTimestamp) -> Result<Self, RetryAfterError> {
70        if value.is_empty() {
71            return Err(RetryAfterError::Empty);
72        }
73        if value.iter().all(u8::is_ascii_digit) {
74            return parse_decimal(value).map(DelaySeconds::new).map(Self::Delay);
75        }
76        parse_http_date(value, now).map(Self::HttpDate)
77    }
78}
79
80fn parse_decimal(value: &[u8]) -> Result<u64, RetryAfterError> {
81    let mut parsed = 0_u64;
82    for byte in value {
83        parsed = parsed
84            .checked_mul(10)
85            .and_then(|current| current.checked_add(u64::from(*byte & 0x0f)))
86            .ok_or(RetryAfterError::Overflow)?;
87    }
88    Ok(parsed)
89}
90
91/// Invalid `Retry-After` syntax or value.
92#[derive(Clone, Copy, Debug, Eq, PartialEq)]
93pub enum RetryAfterError {
94    /// The value was empty.
95    Empty,
96    /// The delay or date cannot be represented.
97    Overflow,
98    /// The value was neither decimal delay-seconds nor a supported HTTP date.
99    InvalidSyntax,
100    /// A date component was outside its valid range.
101    InvalidDate,
102    /// The advertised weekday contradicted the calendar date.
103    WeekdayMismatch,
104}
105
106impl_static_error!(RetryAfterError,
107    Self::Empty => "Retry-After value is empty",
108    Self::Overflow => "Retry-After value exceeds its numeric range",
109    Self::InvalidSyntax => "Retry-After syntax is invalid",
110    Self::InvalidDate => "Retry-After date is invalid",
111    Self::WeekdayMismatch => "Retry-After weekday does not match its date",
112);