headers/common/
retry_after.rs

1use std::time::{Duration, SystemTime};
2
3use http::HeaderValue;
4
5use crate::util::{HttpDate, Seconds, TryFromValues};
6use crate::Error;
7
8/// The `Retry-After` header.
9///
10/// The `Retry-After` response-header field can be used with a 503 (Service
11/// Unavailable) response to indicate how long the service is expected to be
12/// unavailable to the requesting client. This field MAY also be used with any
13/// 3xx (Redirection) response to indicate the minimum time the user-agent is
14/// asked wait before issuing the redirected request. The value of this field
15/// can be either an HTTP-date or an integer number of seconds (in decimal)
16/// after the time of the response.
17///
18/// # Examples
19/// ```
20/// use std::time::{Duration, SystemTime};
21/// use headers::RetryAfter;
22///
23/// let delay = RetryAfter::delay(Duration::from_secs(300));
24/// let date = RetryAfter::date(SystemTime::now());
25/// ```
26///
27/// Retry-After header, defined in [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3)
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct RetryAfter(After);
30
31derive_header! {
32    RetryAfter(_),
33    name: RETRY_AFTER
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
37enum After {
38    /// Retry after the given DateTime
39    DateTime(HttpDate),
40    /// Retry after this duration has elapsed
41    Delay(Seconds),
42}
43
44impl RetryAfter {
45    /// Create an `RetryAfter` header with a date value.
46    pub fn date(time: SystemTime) -> RetryAfter {
47        RetryAfter(After::DateTime(time.into()))
48    }
49
50    /// Create an `RetryAfter` header with a date value.
51    pub fn delay(dur: Duration) -> RetryAfter {
52        RetryAfter(After::Delay(dur.into()))
53    }
54}
55
56impl TryFromValues for After {
57    fn try_from_values<'i, I>(values: &mut I) -> Result<Self, Error>
58    where
59        I: Iterator<Item = &'i HeaderValue>,
60    {
61        values
62            .next()
63            .and_then(|val| {
64                if let Some(delay) = Seconds::from_val(val) {
65                    return Some(After::Delay(delay));
66                }
67
68                let date = HttpDate::from_val(val)?;
69                Some(After::DateTime(date))
70            })
71            .ok_or_else(Error::invalid)
72    }
73}
74
75impl<'a> From<&'a After> for HeaderValue {
76    fn from(after: &'a After) -> HeaderValue {
77        match *after {
78            After::Delay(ref delay) => delay.into(),
79            After::DateTime(ref date) => date.into(),
80        }
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use std::time::Duration;
87
88    use super::super::test_decode;
89    use super::RetryAfter;
90    use crate::util::HttpDate;
91
92    #[test]
93    fn delay_decode() {
94        let r: RetryAfter = test_decode(&["1234"]).unwrap();
95        assert_eq!(r, RetryAfter::delay(Duration::from_secs(1234)),);
96    }
97
98    macro_rules! test_retry_after_datetime {
99        ($name:ident, $s:expr) => {
100            #[test]
101            fn $name() {
102                let r: RetryAfter = test_decode(&[$s]).unwrap();
103                let dt = "Sun, 06 Nov 1994 08:49:37 GMT".parse::<HttpDate>().unwrap();
104
105                assert_eq!(r, RetryAfter(super::After::DateTime(dt)));
106            }
107        };
108    }
109
110    test_retry_after_datetime!(date_decode_rfc1123, "Sun, 06 Nov 1994 08:49:37 GMT");
111    test_retry_after_datetime!(date_decode_rfc850, "Sunday, 06-Nov-94 08:49:37 GMT");
112    test_retry_after_datetime!(date_decode_asctime, "Sun Nov  6 08:49:37 1994");
113}