Skip to main content

leyline/core/retry/
wait.rs

1use 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}