1#![doc = include_str!("../README.md")]
2
3mod convert;
4mod error;
5mod parser;
6mod reset_time;
7mod vendors;
8
9pub mod headers;
10pub mod retry_after;
11
12use std::str::FromStr;
13
14use error::{Error, Result};
15#[cfg(feature = "http")]
16use http::HeaderMap;
17
18use std::time::Duration;
19
20pub use headers::Headers;
21pub use reset_time::ResetTime;
22pub use vendors::{Vendor, VendorMask};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum Status {
27 Ready,
30 Wait(Duration),
33}
34
35#[derive(Copy, Clone, Debug, PartialEq)]
47pub enum RateLimit {
48 Rfc6585(headers::Headers),
50 RetryAfter(retry_after::RateLimit),
52}
53
54impl RateLimit {
55 #[cfg(feature = "http")]
57 pub fn new(headers: &HeaderMap) -> std::result::Result<Self, Error> {
58 Self::extract(crate::convert::header_map_str_pairs(headers))
59 }
60
61 pub fn extract<'a, I>(headers: I) -> std::result::Result<Self, Error>
63 where
64 I: IntoIterator<Item = (&'a str, &'a str)> + Clone,
65 {
66 let rfc6585 = headers::Headers::extract(headers.clone());
67 let retryafter = retry_after::RateLimit::extract(headers);
68
69 match (rfc6585, retryafter) {
70 (Ok(rfc6585), Ok(retryafter)) => {
71 if rfc6585.reset.duration() > retryafter.reset.duration() {
74 Ok(Self::Rfc6585(rfc6585))
75 } else {
76 Ok(Self::RetryAfter(retryafter))
77 }
78 }
79 (Ok(rfc6585), Err(_)) => Ok(Self::Rfc6585(rfc6585)),
80 (Err(_), Ok(retryafter)) => Ok(Self::RetryAfter(retryafter)),
81 (Err(e), Err(_)) => Err(e),
82 }
83 }
84
85 pub const fn is_limited(&self) -> bool {
87 match self {
88 Self::Rfc6585(headers) => headers.remaining == 0,
89 Self::RetryAfter(_) => true,
90 }
91 }
92
93 pub fn status(&self) -> Status {
95 if self.is_limited() {
96 Status::Wait(self.reset().duration())
97 } else {
98 Status::Ready
99 }
100 }
101
102 pub const fn reset(&self) -> ResetTime {
105 match self {
106 Self::Rfc6585(rfc6585) => rfc6585.reset,
107 Self::RetryAfter(retryafter) => retryafter.reset,
108 }
109 }
110
111 pub const fn limit(&self) -> Option<usize> {
115 match self {
116 Self::Rfc6585(rfc6585) => Some(rfc6585.limit),
117 Self::RetryAfter(_) => None,
118 }
119 }
120
121 pub const fn remaining(&self) -> Option<usize> {
125 match self {
126 Self::Rfc6585(rfc6585) => Some(rfc6585.remaining),
127 Self::RetryAfter(_) => None,
128 }
129 }
130}
131
132impl FromStr for RateLimit {
133 type Err = Error;
134
135 fn from_str(map: &str) -> Result<Self> {
136 RateLimit::extract(crate::convert::parse_header_lines(map))
137 }
138}
139
140#[cfg(feature = "reqwest")]
141impl TryFrom<&reqwest::Response> for RateLimit {
142 type Error = Error;
143
144 fn try_from(response: &reqwest::Response) -> Result<Self> {
145 Self::extract(crate::convert::header_map_str_pairs(response.headers()))
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use indoc::indoc;
153 use std::str::FromStr;
154 use time::macros::datetime;
155
156 use crate::reset_time::ResetTime;
157
158 #[test]
159 fn use_later_reset_time_date() {
160 let headers = indoc! {"
161 X-Ratelimit-Used: 100
162 X-Ratelimit-Remaining: 22
163 X-Ratelimit-Reset: 30
164 Retry-After: Wed, 21 Oct 2099 07:28:00 GMT
165 "};
166
167 let rate = RateLimit::from_str(headers).unwrap();
168 assert_eq!(
169 rate.reset(),
170 ResetTime::DateTime(datetime!(2099-10-21 7:28:00.0 UTC))
171 );
172 }
173
174 #[test]
175 fn use_later_reset_time_seconds() {
176 let headers = indoc! {"
177 X-Ratelimit-Used: 100
178 X-Ratelimit-Remaining: 22
179 X-Ratelimit-Reset: 30
180 Retry-After: 20
181 "};
182
183 let rate = RateLimit::from_str(headers).unwrap();
184 assert_eq!(rate.reset(), ResetTime::Seconds(30));
185 }
186
187 #[test]
188 fn test_status_is_limited() {
189 let headers = indoc! {"
190 RateLimit-Limit: 10
191 RateLimit-Remaining: 0
192 RateLimit-Reset: 30
193 "};
194 let rate = RateLimit::from_str(headers).unwrap();
195 assert!(rate.is_limited());
196 match rate.status() {
197 Status::Wait(d) => assert_eq!(d, std::time::Duration::from_secs(30)),
198 _ => panic!("Expected Status::Wait"),
199 }
200
201 let headers = indoc! {"
202 RateLimit-Limit: 10
203 RateLimit-Remaining: 1
204 RateLimit-Reset: 30
205 "};
206 let rate = RateLimit::from_str(headers).unwrap();
207 assert!(!rate.is_limited());
208 assert_eq!(rate.status(), Status::Ready);
209 }
210}