Skip to main content

rate_limits/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(clippy::all)]
3#![warn(
4    absolute_paths_not_starting_with_crate,
5    rustdoc::invalid_html_tags,
6    missing_copy_implementations,
7    missing_debug_implementations,
8    semicolon_in_expressions_from_macros,
9    unreachable_pub,
10    unused_extern_crates,
11    variant_size_differences,
12    clippy::missing_const_for_fn
13)]
14#![deny(anonymous_parameters, macro_use_extern_crate)]
15#![deny(missing_docs)]
16#![allow(clippy::module_name_repetitions)]
17
18mod casesensitive_headermap;
19mod convert;
20mod error;
21mod reset_time;
22
23pub mod headers;
24pub mod retryafter;
25
26use std::str::FromStr;
27
28use casesensitive_headermap::CaseSensitiveHeaderMap;
29use error::{Error, Result};
30
31pub use headers::{Headers, Vendor};
32pub use reset_time::ResetTime;
33
34/// Rate Limit information, parsed from HTTP headers.
35///
36/// There are multiple ways to represent rate limit information in HTTP headers.
37/// The following variants are supported:
38///
39/// - [IETF "Polly" draft][ietf]
40/// - [Retry-After][retryafter]
41///
42/// [ietf]: https://datatracker.ietf.org/doc/html/draft-polli-ratelimit-headers-00
43/// [retryafter]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After
44///
45#[derive(Debug, Copy, Clone, PartialEq)]
46pub enum RateLimit {
47    /// Rate limit information as per the [IETF "Polly" draft][ietf].
48    Rfc6585(headers::Headers),
49    /// Rate limit information as per the [Retry-After][retryafter] header.
50    RetryAfter(retryafter::RateLimit),
51}
52
53impl RateLimit {
54    /// Create a new `RateLimit` from a `http::HeaderMap`.
55    pub fn new<T: Into<CaseSensitiveHeaderMap>>(headers: T) -> std::result::Result<Self, Error> {
56        let headers = headers.into();
57        let rfc6585 = headers::Headers::new(headers.clone());
58        let retryafter = retryafter::RateLimit::new(headers);
59
60        match (rfc6585, retryafter) {
61            (Ok(rfc6585), Ok(retryafter)) => {
62                if rfc6585.reset > retryafter.reset {
63                    Ok(Self::Rfc6585(rfc6585))
64                } else {
65                    Ok(Self::RetryAfter(retryafter))
66                }
67            }
68            (Ok(rfc6585), Err(_)) => Ok(Self::Rfc6585(rfc6585)),
69            (Err(_), Ok(retryafter)) => Ok(Self::RetryAfter(retryafter)),
70            (Err(e), Err(_)) => Err(e),
71        }
72    }
73
74    /// Get `reset` time.
75    /// This is the time when the rate limit will be reset.
76    pub const fn reset(&self) -> ResetTime {
77        match self {
78            Self::Rfc6585(rfc6585) => rfc6585.reset,
79            Self::RetryAfter(retryafter) => retryafter.reset,
80        }
81    }
82
83    /// Get `limit` value.
84    ///
85    /// This is the maximum number of requests that can be made in a given time window.
86    pub const fn limit(&self) -> Option<usize> {
87        match self {
88            Self::Rfc6585(rfc6585) => Some(rfc6585.limit),
89            Self::RetryAfter(_) => None,
90        }
91    }
92
93    /// Get `remaining` value.
94    ///
95    /// This is the number of requests remaining in the current time window.
96    pub const fn remaining(&self) -> Option<usize> {
97        match self {
98            Self::Rfc6585(rfc6585) => Some(rfc6585.remaining),
99            Self::RetryAfter(_) => None,
100        }
101    }
102}
103
104impl FromStr for RateLimit {
105    type Err = Error;
106
107    fn from_str(map: &str) -> Result<Self> {
108        RateLimit::new(map)
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use indoc::indoc;
116    use std::str::FromStr;
117    use time::macros::datetime;
118
119    use crate::reset_time::ResetTime;
120
121    #[test]
122    fn use_later_reset_time_date() {
123        let headers = indoc! {"
124            X-Ratelimit-Used: 100
125            X-Ratelimit-Remaining: 22
126            X-Ratelimit-Reset: 30
127            Retry-After: Wed, 21 Oct 2015 07:28:00 GMT
128        "};
129
130        let rate = RateLimit::from_str(headers).unwrap();
131        assert_eq!(
132            rate.reset(),
133            ResetTime::DateTime(datetime!(2015-10-21 7:28:00.0 UTC))
134        );
135    }
136
137    #[test]
138    fn use_later_reset_time_seconds() {
139        let headers = indoc! {"
140            X-Ratelimit-Used: 100
141            X-Ratelimit-Remaining: 22
142            X-Ratelimit-Reset: 30
143            Retry-After: 20
144        "};
145
146        let rate = RateLimit::from_str(headers).unwrap();
147        assert_eq!(rate.reset(), ResetTime::Seconds(30));
148    }
149}