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#[derive(Debug, Copy, Clone, PartialEq)]
46pub enum RateLimit {
47 Rfc6585(headers::Headers),
49 RetryAfter(retryafter::RateLimit),
51}
52
53impl RateLimit {
54 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 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 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 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}