leyline/core/retry/
wait.rs1use std::time::{Duration, SystemTime};
2
3use crate::util::epoch_plus;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6#[non_exhaustive]
7pub enum WaitFormat {
8 Seconds,
9 UnixSeconds,
10 HttpDate,
11}
12
13#[derive(Debug, Clone)]
14pub(crate) struct WaitHeader {
15 pub(crate) name: String,
16 pub(crate) format: WaitFormat,
17}
18
19pub(crate) const RETRY_AFTER: &str = "retry-after";
20const RETRY_AFTER_FORMATS: [WaitFormat; 2] = [WaitFormat::Seconds, WaitFormat::HttpDate];
21
22impl WaitFormat {
23 pub(crate) fn parse(self, value: &str) -> Option<Duration> {
24 let value = value.trim();
25 match self {
26 Self::Seconds => parse_seconds(value),
27 Self::UnixSeconds => until(epoch_plus(parse_seconds(value)?)?),
28 Self::HttpDate => until(parse_imf_fixdate(value)?),
29 }
30 }
31}
32
33pub(crate) fn parse_retry_after(value: &str) -> Option<Duration> {
34 RETRY_AFTER_FORMATS
35 .iter()
36 .find_map(|format| format.parse(value))
37}
38
39fn parse_seconds(value: &str) -> Option<Duration> {
40 if let Ok(secs) = value.parse::<u64>() {
41 return Some(Duration::from_secs(secs));
42 }
43 let secs = value.parse::<f64>().ok()?;
44 Duration::try_from_secs_f64(secs).ok()
45}
46
47fn until(when: SystemTime) -> Option<Duration> {
48 Some(
49 when.duration_since(SystemTime::now())
50 .unwrap_or(Duration::ZERO),
51 )
52}
53
54fn parse_imf_fixdate(s: &str) -> Option<SystemTime> {
55 let rest = s.split_once(", ")?.1;
56 let mut parts = rest.split_whitespace();
57 let day: u32 = parts.next()?.parse().ok()?;
58 let month = month_num(parts.next()?)?;
59 let year: i32 = parts.next()?.parse().ok()?;
60 let hms = parts.next()?;
61 let tz = parts.next()?;
62 if !tz.eq_ignore_ascii_case("GMT") && !tz.eq_ignore_ascii_case("UTC") {
63 return None;
64 }
65 let mut t = hms.split(':');
66 let hour: u32 = t.next()?.parse().ok()?;
67 let min: u32 = t.next()?.parse().ok()?;
68 let sec: u32 = t.next()?.parse().ok()?;
69 unix_from_ymd_hms(year, month, day, hour, min, sec)
70}
71
72const MONTHS: [&str; 12] = [
73 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
74];
75
76const IMF_FIXDATE_YEARS: std::ops::RangeInclusive<i32> = 1970..=9999;
77
78fn month_num(month: &str) -> Option<u32> {
79 let index = MONTHS.iter().position(|m| *m == month)?;
80 u32::try_from(index + 1).ok()
81}
82
83pub(super) fn unix_from_ymd_hms(
84 year: i32,
85 month: u32,
86 day: u32,
87 hour: u32,
88 min: u32,
89 sec: u32,
90) -> Option<SystemTime> {
91 if !date_in_range(year, month, day) || hour > 23 || min > 59 || sec > 59 {
92 return None;
93 }
94 let secs = days_from_civil(year, month, day)?
95 .checked_mul(86400)?
96 .checked_add(i64::from(hour) * 3600 + i64::from(min) * 60 + i64::from(sec))?;
97 epoch_plus(Duration::from_secs(u64::try_from(secs).ok()?))
98}
99
100fn date_in_range(year: i32, month: u32, day: u32) -> bool {
101 IMF_FIXDATE_YEARS.contains(&year) && (1..=12).contains(&month) && (1..=31).contains(&day)
102}
103
104fn days_from_civil(year: i32, month: u32, day: u32) -> Option<i64> {
105 let y = if month <= 2 {
106 year.checked_sub(1)?
107 } else {
108 year
109 };
110 let era = y.div_euclid(400);
111 let yoe = u32::try_from(y.checked_sub(era.checked_mul(400)?)?).ok()?;
112 let shifted = if month > 2 { month - 3 } else { month + 9 };
113 let doy = (153 * shifted + 2) / 5 + day - 1;
114 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
115 Some(i64::from(era) * 146097 + i64::from(doe) - 719468)
116}