http_types/other/
retry_after.rs1use std::time::{Duration, SystemTime, SystemTimeError};
2
3use crate::headers::{HeaderName, HeaderValue, Headers, RETRY_AFTER};
4use crate::utils::{fmt_http_date, parse_http_date};
5
6#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
36pub struct RetryAfter {
37 inner: RetryDirective,
38}
39
40#[allow(clippy::len_without_is_empty)]
41impl RetryAfter {
42 pub fn new(dur: Duration) -> Self {
46 Self {
47 inner: RetryDirective::Duration(dur),
48 }
49 }
50
51 pub fn new_at(at: SystemTime) -> Self {
55 Self {
56 inner: RetryDirective::SystemTime(at),
57 }
58 }
59
60 pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
62 let header = match headers.as_ref().get(RETRY_AFTER) {
63 Some(headers) => headers.last(),
64 None => return Ok(None),
65 };
66
67 let inner = match header.as_str().parse::<u64>() {
68 Ok(dur) => RetryDirective::Duration(Duration::from_secs(dur)),
69 Err(_) => {
70 let at = parse_http_date(header.as_str())?;
71 RetryDirective::SystemTime(at)
72 }
73 };
74 Ok(Some(Self { inner }))
75 }
76
77 pub fn duration_since(&self, earlier: SystemTime) -> Result<Duration, SystemTimeError> {
83 let at = match self.inner {
84 RetryDirective::Duration(dur) => SystemTime::now() + dur,
85 RetryDirective::SystemTime(at) => at,
86 };
87
88 at.duration_since(earlier)
89 }
90
91 pub fn apply(&self, mut headers: impl AsMut<Headers>) {
93 headers.as_mut().insert(self.name(), self.value());
94 }
95
96 pub fn name(&self) -> HeaderName {
98 RETRY_AFTER
99 }
100
101 pub fn value(&self) -> HeaderValue {
103 let output = match self.inner {
104 RetryDirective::Duration(dur) => format!("{}", dur.as_secs()),
105 RetryDirective::SystemTime(at) => fmt_http_date(at),
106 };
107 unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
109 }
110}
111
112impl From<RetryAfter> for SystemTime {
113 fn from(retry_after: RetryAfter) -> Self {
114 match retry_after.inner {
115 RetryDirective::Duration(dur) => SystemTime::now() + dur,
116 RetryDirective::SystemTime(at) => at,
117 }
118 }
119}
120
121#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
126enum RetryDirective {
127 Duration(Duration),
128 SystemTime(SystemTime),
129}
130
131#[cfg(test)]
132mod test {
133 use super::*;
134 use crate::headers::Headers;
135
136 #[test]
137 fn smoke() -> crate::Result<()> {
138 let retry = RetryAfter::new(Duration::from_secs(10));
139
140 let mut headers = Headers::new();
141 retry.apply(&mut headers);
142
143 let now = SystemTime::now();
146 let retry = RetryAfter::from_headers(headers)?.unwrap();
147 assert_eq!(
148 retry.duration_since(now)?.as_secs(),
149 Duration::from_secs(10).as_secs()
150 );
151 Ok(())
152 }
153
154 #[test]
155 fn new_at() -> crate::Result<()> {
156 let now = SystemTime::now();
157 let retry = RetryAfter::new_at(now + Duration::from_secs(10));
158
159 let mut headers = Headers::new();
160 retry.apply(&mut headers);
161
162 let retry = RetryAfter::from_headers(headers)?.unwrap();
165 let delta = retry.duration_since(now)?;
166 assert!(delta >= Duration::from_secs(9));
167 assert!(delta <= Duration::from_secs(10));
168 Ok(())
169 }
170}