http_types/other/
retry_after.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
use std::time::{Duration, SystemTime, SystemTimeError};

use crate::headers::{HeaderName, HeaderValue, Headers, RETRY_AFTER};
use crate::utils::{fmt_http_date, parse_http_date};

/// Indicate how long the user agent should wait before making a follow-up request.
///
/// [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After)
///
/// # Specifications
///
/// - [RFC 7231, section 3.1.4.2: Retry-After](https://tools.ietf.org/html/rfc7231#section-3.1.4.2)
///
/// # Examples
///
/// ```no_run
/// # fn main() -> http_types::Result<()> {
/// #
/// use http_types::other::RetryAfter;
/// use http_types::Response;
/// use std::time::{SystemTime, Duration};
/// use async_std::task;
///
/// let retry = RetryAfter::new(Duration::from_secs(10));
///
/// let mut headers = Response::new(429);
/// retry.apply(&mut headers);
///
/// // Sleep for the duration, then try the task again.
/// let retry = RetryAfter::from_headers(headers)?.unwrap();
/// task::sleep(retry.duration_since(SystemTime::now())?);
/// #
/// # Ok(()) }
/// ```
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct RetryAfter {
    inner: RetryDirective,
}

#[allow(clippy::len_without_is_empty)]
impl RetryAfter {
    /// Create a new instance from a `Duration`.
    ///
    /// This value will be encoded over the wire as a relative offset in seconds.
    pub fn new(dur: Duration) -> Self {
        Self {
            inner: RetryDirective::Duration(dur),
        }
    }

    /// Create a new instance from a `SystemTime` instant.
    ///
    /// This value will be encoded a specific `Date` over the wire.
    pub fn new_at(at: SystemTime) -> Self {
        Self {
            inner: RetryDirective::SystemTime(at),
        }
    }

    /// Create a new instance from headers.
    pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
        let header = match headers.as_ref().get(RETRY_AFTER) {
            Some(headers) => headers.last(),
            None => return Ok(None),
        };

        let inner = match header.as_str().parse::<u64>() {
            Ok(dur) => RetryDirective::Duration(Duration::from_secs(dur)),
            Err(_) => {
                let at = parse_http_date(header.as_str())?;
                RetryDirective::SystemTime(at)
            }
        };
        Ok(Some(Self { inner }))
    }

    /// Returns the amount of time elapsed from an earlier point in time.
    ///
    /// # Errors
    ///
    /// This may return an error if the `earlier` time was after the current time.
    pub fn duration_since(&self, earlier: SystemTime) -> Result<Duration, SystemTimeError> {
        let at = match self.inner {
            RetryDirective::Duration(dur) => SystemTime::now() + dur,
            RetryDirective::SystemTime(at) => at,
        };

        at.duration_since(earlier)
    }

    /// Sets the header.
    pub fn apply(&self, mut headers: impl AsMut<Headers>) {
        headers.as_mut().insert(self.name(), self.value());
    }

    /// Get the `HeaderName`.
    pub fn name(&self) -> HeaderName {
        RETRY_AFTER
    }

    /// Get the `HeaderValue`.
    pub fn value(&self) -> HeaderValue {
        let output = match self.inner {
            RetryDirective::Duration(dur) => format!("{}", dur.as_secs()),
            RetryDirective::SystemTime(at) => fmt_http_date(at),
        };
        // SAFETY: the internal string is validated to be ASCII.
        unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
    }
}

impl From<RetryAfter> for SystemTime {
    fn from(retry_after: RetryAfter) -> Self {
        match retry_after.inner {
            RetryDirective::Duration(dur) => SystemTime::now() + dur,
            RetryDirective::SystemTime(at) => at,
        }
    }
}

/// What value are we decoding into?
///
/// This value is intionally never exposes; all end-users want is a `Duration`
/// value that tells them how long to wait for before trying again.
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
enum RetryDirective {
    Duration(Duration),
    SystemTime(SystemTime),
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::headers::Headers;

    #[test]
    fn smoke() -> crate::Result<()> {
        let retry = RetryAfter::new(Duration::from_secs(10));

        let mut headers = Headers::new();
        retry.apply(&mut headers);

        // `SystemTime::now` uses sub-second precision which means there's some
        // offset that's not encoded.
        let now = SystemTime::now();
        let retry = RetryAfter::from_headers(headers)?.unwrap();
        assert_eq!(
            retry.duration_since(now)?.as_secs(),
            Duration::from_secs(10).as_secs()
        );
        Ok(())
    }

    #[test]
    fn new_at() -> crate::Result<()> {
        let now = SystemTime::now();
        let retry = RetryAfter::new_at(now + Duration::from_secs(10));

        let mut headers = Headers::new();
        retry.apply(&mut headers);

        // `SystemTime::now` uses sub-second precision which means there's some
        // offset that's not encoded.
        let retry = RetryAfter::from_headers(headers)?.unwrap();
        let delta = retry.duration_since(now)?;
        assert!(delta >= Duration::from_secs(9));
        assert!(delta <= Duration::from_secs(10));
        Ok(())
    }
}