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);