hyperx/header/common/
retry_after.rs

1// Copyright (c) 2016 retry-after Developers
2//
3// This file is dual licensed under MIT and Apache 2.0
4//
5// *******************************************************
6//
7// Permission is hereby granted, free of charge, to any
8// person obtaining a copy of this software and associated
9// documentation files (the "Software"), to deal in the
10// Software without restriction, including without
11// limitation the rights to use, copy, modify, merge,
12// publish, distribute, sublicense, and/or sell copies of
13// the Software, and to permit persons to whom the Software
14// is furnished to do so, subject to the following
15//
16// conditions:
17//
18// The above copyright notice and this permission notice
19// shall be included in all copies or substantial portions
20// of the Software.
21//
22// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
23// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
24// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
25// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
26// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
27// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
29// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
30// DEALINGS IN THE SOFTWARE.
31//
32// *******************************************************
33//
34// Apache License
35// Version 2.0, January 2004
36// http://www.apache.org/licenses/
37
38use std::fmt;
39use std::time::Duration;
40
41use header::{Header, RawLike};
42use header::shared::HttpDate;
43
44/// The `Retry-After` header.
45///
46/// The `Retry-After` response-header field can be used with a 503 (Service
47/// Unavailable) response to indicate how long the service is expected to be
48/// unavailable to the requesting client. This field MAY also be used with any
49/// 3xx (Redirection) response to indicate the minimum time the user-agent is
50/// asked wait before issuing the redirected request. The value of this field
51/// can be either an HTTP-date or an integer number of seconds (in decimal)
52/// after the time of the response.
53///
54/// # Examples
55/// ```
56/// # extern crate http;
57/// use std::time::Duration;
58/// use hyperx::header::{RetryAfter, TypedHeaders};
59///
60/// let mut headers = http::HeaderMap::new();
61/// headers.encode(
62///     &RetryAfter::Delay(Duration::from_secs(300))
63/// );
64/// ```
65/// ```
66/// # extern crate http;
67/// use std::time::{SystemTime, Duration};
68/// use hyperx::header::{RetryAfter, TypedHeaders};
69///
70/// let mut headers = http::HeaderMap::new();
71/// let date = SystemTime::now() + Duration::from_secs(300);
72/// headers.encode(
73///     &RetryAfter::DateTime(date.into())
74/// );
75/// ```
76
77/// Retry-After header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.3)
78#[derive(Debug, Copy, Clone, PartialEq, Eq)]
79pub enum RetryAfter {
80    /// Retry after this duration has elapsed
81    ///
82    /// This can be coupled with a response time header to produce a DateTime.
83    Delay(Duration),
84
85    /// Retry after the given DateTime
86    DateTime(HttpDate),
87}
88
89impl Header for RetryAfter {
90    fn header_name() -> &'static str {
91        static NAME: &'static str = "Retry-After";
92        NAME
93    }
94
95    fn parse_header<'a, T>(raw: &'a T) -> ::Result<RetryAfter>
96    where T: RawLike<'a>
97    {
98        if let Some(ref line) = raw.one() {
99            let utf8_str = match ::std::str::from_utf8(line) {
100                Ok(utf8_str) => utf8_str,
101                Err(_) => return Err(::Error::Header),
102            };
103
104            if let Ok(datetime) = utf8_str.parse::<HttpDate>() {
105                return Ok(RetryAfter::DateTime(datetime))
106            }
107
108            if let Ok(seconds) = utf8_str.parse::<u64>() {
109                return Ok(RetryAfter::Delay(Duration::from_secs(seconds)));
110            }
111
112            Err(::Error::Header)
113        } else {
114            Err(::Error::Header)
115        }
116    }
117
118    fn fmt_header(&self, f: &mut ::header::Formatter) -> ::std::fmt::Result {
119        f.fmt_line(self)
120    }
121}
122
123impl fmt::Display for RetryAfter {
124    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
125        match *self {
126            RetryAfter::Delay(ref duration) => {
127                write!(f, "{}", duration.as_secs())
128            },
129            RetryAfter::DateTime(ref datetime) => {
130                fmt::Display::fmt(datetime, f)
131            }
132        }
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use std::time::Duration;
139    use header::{Header, Raw};
140    use header::shared::HttpDate;
141
142    use super::RetryAfter;
143
144    #[test]
145    fn header_name_regression() {
146        assert_eq!(RetryAfter::header_name(), "Retry-After");
147    }
148
149    #[test]
150    fn parse_delay() {
151        let r: Raw = vec![b"1234".to_vec()].into();
152        let retry_after = RetryAfter::parse_header(&r).unwrap();
153        assert_eq!(RetryAfter::Delay(Duration::from_secs(1234)), retry_after);
154    }
155
156    macro_rules! test_retry_after_datetime {
157        ($name:ident, $bytes:expr) => {
158            #[test]
159            fn $name() {
160                let dt = "Sun, 06 Nov 1994 08:49:37 GMT".parse::<HttpDate>().unwrap();
161                let r: Raw = vec![$bytes.to_vec()].into();
162                let retry_after = RetryAfter::parse_header(&r).expect("parse_header ok");
163                assert_eq!(RetryAfter::DateTime(dt), retry_after);
164            }
165        }
166    }
167
168    test_retry_after_datetime!(header_parse_rfc1123, b"Sun, 06 Nov 1994 08:49:37 GMT");
169    test_retry_after_datetime!(header_parse_rfc850, b"Sunday, 06-Nov-94 08:49:37 GMT");
170    test_retry_after_datetime!(header_parse_asctime, b"Sun Nov  6 08:49:37 1994");
171
172    #[test]
173    fn hyper_headers_from_raw_delay() {
174        let r: Raw = b"300".to_vec().into();
175        let retry_after = RetryAfter::parse_header(&r).unwrap();
176        assert_eq!(retry_after, RetryAfter::Delay(Duration::from_secs(300)));
177    }
178
179    #[test]
180    fn hyper_headers_from_raw_datetime() {
181        let r: Raw = b"Sun, 06 Nov 1994 08:49:37 GMT".to_vec().into();
182        let retry_after = RetryAfter::parse_header(&r).unwrap();
183        let expected = "Sun, 06 Nov 1994 08:49:37 GMT".parse::<HttpDate>().unwrap();
184        assert_eq!(retry_after, RetryAfter::DateTime(expected));
185    }
186}
187
188standard_header!(RetryAfter, RETRY_AFTER);