Skip to main content

walker_common/
http.rs

1use std::time::Duration;
2
3use reqwest::{Response, StatusCode, header};
4
5pub enum RetryAfter {
6    Duration(Duration),
7    After(std::time::SystemTime),
8}
9
10/// Parse Retry-After header value.
11/// Supports both delay-seconds (numeric) and HTTP-date formats as per RFC7231
12fn parse_retry_after(value: &str) -> Option<RetryAfter> {
13    // Try parsing as seconds (numeric)
14    if let Ok(seconds) = value.parse::<u64>() {
15        return Some(RetryAfter::Duration(Duration::from_secs(seconds)));
16    }
17
18    // Try parsing as HTTP-date (RFC7231 format)
19    // Common formats: "Sun, 06 Nov 1994 08:49:37 GMT" (IMF-fixdate preferred)
20    if let Ok(datetime) = httpdate::parse_http_date(value) {
21        return Some(RetryAfter::After(datetime));
22    }
23
24    None
25}
26
27pub fn calculate_retry_after_from_response_header(
28    response: &Response,
29    default_duration: Duration,
30) -> Option<Duration> {
31    if response.status() == StatusCode::TOO_MANY_REQUESTS {
32        let retry_after = response
33            .headers()
34            .get(header::RETRY_AFTER)
35            .and_then(|v| v.to_str().ok())
36            .and_then(parse_retry_after)
37            .and_then(|retry| match retry {
38                RetryAfter::Duration(d) => Some(d),
39                RetryAfter::After(after) => {
40                    // Calculate duration from now until the specified time
41                    std::time::SystemTime::now()
42                        .duration_since(std::time::UNIX_EPOCH)
43                        .ok()
44                        .and_then(|now| {
45                            after
46                                .duration_since(std::time::UNIX_EPOCH)
47                                .ok()
48                                .and_then(|target| target.checked_sub(now))
49                        })
50                }
51            })
52            .unwrap_or(default_duration);
53        return Some(retry_after);
54    }
55    None
56}
57
58pub fn get_client_error(response: &Response) -> Option<StatusCode> {
59    let status = response.status();
60    if status.is_client_error() {
61        Some(status)
62    } else {
63        None
64    }
65}