forked from lycheeverse/lychee
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathheaders.rs
More file actions
102 lines (86 loc) · 3.14 KB
/
headers.rs
File metadata and controls
102 lines (86 loc) · 3.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! Handle rate limiting headers.
//! Note that we might want to replace this module with
//! <https://github.com/mre/rate-limits> at some point in the future.
use http::HeaderValue;
use std::time::{Duration, SystemTime};
use thiserror::Error;
#[derive(Debug, Error, PartialEq, Eq)]
pub(crate) enum RetryAfterParseError {
#[error("Unable to parse value '{0}'")]
ValueError(String),
#[error("Header value contains invalid chars")]
HeaderValueError,
}
/// Parse the "Retry-After" header as specified per
/// [RFC 7231 section 7.1.3](https://www.rfc-editor.org/rfc/rfc7231#section-7.1.3)
pub(crate) fn parse_retry_after(value: &HeaderValue) -> Result<Duration, RetryAfterParseError> {
let value = value
.to_str()
.map_err(|_| RetryAfterParseError::HeaderValueError)?;
// RFC 7231: Retry-After = HTTP-date / delay-seconds
value.parse::<u64>().map(Duration::from_secs).or_else(|_| {
httpdate::parse_http_date(value)
.map(|s| {
s.duration_since(SystemTime::now())
// if date is in the past, we can use ZERO
.unwrap_or(Duration::ZERO)
})
.map_err(|_| RetryAfterParseError::ValueError(value.into()))
})
}
/// Parse the common "X-RateLimit" header fields.
/// Unfortunately, this is not standardised yet, but there is an
/// [IETF draft](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/).
pub(crate) fn parse_common_rate_limit_header_fields(
headers: &http::HeaderMap,
) -> (Option<usize>, Option<usize>) {
let remaining = self::parse_header_value(
headers,
&[
"x-ratelimit-remaining",
"x-rate-limit-remaining",
"ratelimit-remaining",
],
);
let limit = self::parse_header_value(
headers,
&["x-ratelimit-limit", "x-rate-limit-limit", "ratelimit-limit"],
);
(remaining, limit)
}
/// Helper method to parse numeric header values from common rate limit headers
fn parse_header_value(headers: &http::HeaderMap, header_names: &[&str]) -> Option<usize> {
for header_name in header_names {
if let Some(value) = headers.get(*header_name)
&& let Ok(value_str) = value.to_str()
&& let Ok(number) = value_str.parse()
{
return Some(number);
}
}
None
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use http::HeaderValue;
use crate::ratelimit::headers::{RetryAfterParseError, parse_retry_after};
#[test]
fn test_retry_after() {
assert_eq!(parse_retry_after(&value("1")), Ok(Duration::from_secs(1)));
assert_eq!(
parse_retry_after(&value("-1")),
Err(RetryAfterParseError::ValueError("-1".into()))
);
assert_eq!(
parse_retry_after(&value("Fri, 15 May 2015 15:34:21 GMT")),
Ok(Duration::ZERO)
);
let result = parse_retry_after(&value("Fri, 15 May 4099 15:34:21 GMT"));
let is_in_future = matches!(result, Ok(d) if d.as_secs() > 0);
assert!(is_in_future);
}
fn value(v: &str) -> HeaderValue {
HeaderValue::from_str(v).unwrap()
}
}