Skip to main content

rama_http_headers/common/
retry_after.rs

1use std::time::SystemTime;
2
3use rama_core::error::BoxError;
4use rama_core::telemetry::tracing;
5use rama_http_types::HeaderValue;
6
7use crate::Error;
8use crate::util::{HttpDate, Seconds, TryFromValues};
9
10/// The `Retry-After` header.
11///
12/// The `Retry-After` response-header field can be used with a 503 (Service
13/// Unavailable) response to indicate how long the service is expected to be
14/// unavailable to the requesting client. This field MAY also be used with any
15/// 3xx (Redirection) response to indicate the minimum time the user-agent is
16/// asked wait before issuing the redirected request. The value of this field
17/// can be either an HTTP-date or an integer number of seconds (in decimal)
18/// after the time of the response.
19///
20/// # Examples
21/// ```
22/// use rama_http_headers::{RetryAfter, util::Seconds};
23/// use rama_utils::time::now_system_time;
24///
25/// let delay = RetryAfter::delay(Seconds::new(300));
26/// let date = RetryAfter::date(now_system_time());
27/// ```
28///
29/// Retry-After header, defined in [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3)
30#[derive(Debug, Copy, Clone, PartialEq, Eq)]
31pub struct RetryAfter(After);
32
33impl crate::TypedHeader for RetryAfter {
34    fn name() -> &'static ::rama_http_types::header::HeaderName {
35        &::rama_http_types::header::RETRY_AFTER
36    }
37}
38
39impl crate::HeaderDecode for RetryAfter {
40    fn decode<'i, I>(values: &mut I) -> Result<Self, crate::Error>
41    where
42        I: Iterator<Item = &'i ::rama_http_types::header::HeaderValue>,
43    {
44        crate::util::TryFromValues::try_from_values(values).map(RetryAfter)
45    }
46}
47
48impl crate::HeaderEncode for RetryAfter {
49    fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
50        match HeaderValue::try_from(&self.0) {
51            Ok(value) => values.extend(::std::iter::once(value)),
52            Err(err) => {
53                tracing::debug!("failed to encode retry-after value as header: {err}");
54            }
55        }
56    }
57}
58
59#[derive(Debug, Copy, Clone, PartialEq, Eq)]
60pub enum After {
61    /// Retry after the given DateTime
62    DateTime(HttpDate),
63    /// Retry after this duration has elapsed
64    Delay(Seconds),
65}
66
67impl RetryAfter {
68    /// Create an `RetryAfter` header with a date value.
69    #[must_use]
70    pub fn date(time: SystemTime) -> Self {
71        Self(After::DateTime(time.into()))
72    }
73
74    /// Create an `RetryAfter` header with a delay value in seconds
75    #[must_use]
76    pub fn delay(seconds: Seconds) -> Self {
77        Self(After::Delay(seconds))
78    }
79
80    #[must_use]
81    pub fn after(&self) -> After {
82        self.0
83    }
84}
85
86impl TryFromValues for After {
87    fn try_from_values<'i, I>(values: &mut I) -> Result<Self, Error>
88    where
89        I: Iterator<Item = &'i HeaderValue>,
90    {
91        values
92            .next()
93            .and_then(|val| {
94                if let Some(delay) = Seconds::try_from_val(val) {
95                    return Some(Self::Delay(delay));
96                }
97
98                let date = HttpDate::from_val(val)?;
99                Some(Self::DateTime(date))
100            })
101            .ok_or_else(Error::invalid)
102    }
103}
104
105impl<'a> TryFrom<&'a After> for HeaderValue {
106    type Error = BoxError;
107
108    fn try_from(after: &'a After) -> Result<Self, Self::Error> {
109        match *after {
110            After::Delay(ref delay) => Ok(delay.into()),
111            After::DateTime(ref date) => date.try_into(),
112        }
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::super::test_decode;
119    use super::{RetryAfter, Seconds};
120    use crate::util::HttpDate;
121
122    #[test]
123    fn delay_decode() {
124        let r: RetryAfter = test_decode(&["1234"]).unwrap();
125        assert_eq!(r, RetryAfter::delay(Seconds::new(1234)));
126    }
127
128    macro_rules! test_retry_after_datetime {
129        ($name:ident, $s:expr) => {
130            #[test]
131            fn $name() {
132                let r: RetryAfter = test_decode(&[$s]).unwrap();
133                let dt = "Sun, 06 Nov 1994 08:49:37 GMT".parse::<HttpDate>().unwrap();
134
135                assert_eq!(r, RetryAfter(super::After::DateTime(dt)));
136            }
137        };
138    }
139
140    test_retry_after_datetime!(date_decode_rfc1123, "Sun, 06 Nov 1994 08:49:37 GMT");
141    test_retry_after_datetime!(date_decode_rfc850, "Sunday, 06-Nov-94 08:49:37 GMT");
142    test_retry_after_datetime!(date_decode_asctime, "Sun Nov  6 08:49:37 1994");
143}