tag2upload_service_manager/
utils.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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244

use crate::prelude::*;

use std::time::SystemTime;

define_derive_deftly! {
    export DebugTransparent expect items:

    impl Debug for $tname {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            $(
                Debug::fmt(&self.$fname, f)
            )
        }
    }
}

/// Establish bindings, within a sub-expression
///
/// ```
/// # use tag2upload_service_manager::with_let;
/// # let EXPRESSION = 1;
/// # let BODY = 2;
/// with_let!([
///     BINDING = EXPRESSION;
///     // .. more BINDINGs ...
/// ], {
///     BODY
/// });
/// ```
///
/// Expands to a 1-arm `match` (the "`match` lifetime extension trick).
/// Each subsequent `BINDING` may refer to previous ones.
#[macro_export]
macro_rules! with_let {
    {
        [ ],
        { $($body:tt)* }
    } => {
        { $($body)* }
    };
    {
        [ $bind0:ident = $val0:expr; $($bindn:tt)* ],
        { $($body:tt)* }
    } => {
        match $val0 {
            $bind0 => with_let!( [ $($bindn)* ], { $($body)* } ),
        }
    }
}

#[ext(WatchReceiverExt)]
pub impl<T: Send + Sync> watch::Receiver<T> {
    fn wait_for_then<R: Send + Sync>(
        &mut self,
        mut f: impl FnMut(&T) -> Option<R> + Send + Sync,
    ) -> impl Future<Output = Result<R, watch::error::RecvError>>
        + Send + Sync
    {
        async move {
            let mut r = None;
            self.wait_for(|rx| {
                r = f(&rx);
                r.is_some()
            }).await?;
            Ok(r.expect("watch::Receiver misbehaved"))
        }
    }
}

impl Globals {
    pub fn now_systemtime(&self) -> SystemTime {
        let now = SystemTime::now();

        // We don't care about overflow in here, because the offset
        // is always zero in production.
        {
            let add: i64 = self.config.testing.time_offset;

            #[cfg(test)]
            let add = add + *self.test_suppl.simulated_time_advance
                .lock().expect("simulated time poisoned");

            if add < 0 {
                now - Duration::from_secs(-add as _)
            } else if add > 0 {
                now + Duration::from_secs(add as _)
            } else {
                now
            }
        }
    }

    pub fn now(&self) -> TimeT {
        self.now_systemtime().into()
    }

    pub fn check_tag_recency(
        &self,
        tag_date: SystemTime,
    ) -> Result<IsRecentEnough, NFR> {
        let now_st = self.now_systemtime();

        macro_rules! check_interval_max { {
            $var:ident <= $max:ident else $constructor:ident
        } => {
            let max = self.config.intervals.$max;
            if $var > *max {
                return Err(NFR::$constructor {
                    $var: $var.into(),
                    max: (*max).into(),
                })
            }
        } }

        if let Ok(age) = now_st.duration_since(tag_date) {
            check_interval_max!(age <= max_tag_age else TagTooOld);
        } else if let Ok(skew) = tag_date.duration_since(now_st) {
            check_interval_max!(skew <= max_tag_age_skew else TagTooNew);
        }

        Ok(IsRecentEnough::new_unchecked())
    }
}

/// Token indicating that the tag date has been checked
pub struct IsRecentEnough { _hidden: () }

impl IsRecentEnough {
    pub fn new_unchecked() -> Self {
        IsRecentEnough { _hidden: () }
    }
}

/// `Duration` which displays and parses and serialises with [`humantime`]
///
/// Astonishingly `humantime_serde::Duration` doesn't impl `Display`!
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy)]
#[derive(Into, From, Deref)]
pub struct HtDuration(pub Duration);

impl Serialize for HtDuration {
    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        humantime_serde::Serde::from(self.0).serialize(s)
    }
}
impl<'de> Deserialize<'de> for HtDuration {
    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
        let h = humantime_serde::Serde::<Duration>::deserialize(d)?;
        Ok(HtDuration(*h))
    }
}
impl FromStr for HtDuration {
    type Err = <humantime::Duration as FromStr>::Err;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let h: humantime::Duration = s.parse()?;
        Ok(HtDuration(h.into()))
    }
}
impl Display for HtDuration {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let h = humantime::Duration::from(self.0);
        Display::fmt(&h, f)
    }
}

/// `TimeT` which displays and serialises with [`humantime`]
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy)]
#[derive(Into, From, Deref)]
pub struct HtTimeT(pub TimeT);

impl Serialize for HtTimeT {
    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        humantime_serde::Serde::from(SystemTime::from(self.0)).serialize(s)
    }
}
impl Display for HtTimeT {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let h = humantime::Timestamp::from(SystemTime::from(self.0));
        Display::fmt(&h, f)
    }
}

pub fn unix_access(path: &str, mode: c_int) -> io::Result<()> {
    let path: Vec<NonZeroU8> = path.as_bytes().iter()
        .map(|c| NonZeroU8::try_from(*c))
        .collect::<Result<_, _>>().
        map_err(|e| io::Error::new(
            io::ErrorKind::InvalidInput,
            e,
        ))?;

    let path: CString = path.into();

    // SAFETY: path.as_ptr() is valid - we just made it.
    // We dispose of path later, proving it's still alive;
    let r = unsafe { libc::access(path.as_ptr(), mode) };
    drop::<CString>(path);

    if r == 0 {
        Ok(())
    } else {
        Err(io::Error::last_os_error())
    }
}

#[test]
fn ht_duration() -> test_prelude::TestResult<()> {
    #[derive(Serialize, Deserialize, Debug)]
    struct O {
        d: HtDuration,
    }

    let s = "27m";
    let j = format!(r#"{{"d":{s:?}}}"#);

    let o: O = serde_json::from_str(&j)?;
    let d = HtDuration::from_str(s)?;

    assert_eq!(*d, Duration::from_secs(27 * 60));
    assert_eq!(o.d, d);
    assert_eq!(d.to_string(), s);
    assert_eq!(serde_json::to_string(&o)?, j);

    Ok(())
}

#[test]
fn ht_time_t() -> test_prelude::TestResult<()> {
    #[derive(Serialize, Debug)]
    struct O {
        t: HtTimeT,
    }

    let t = HtTimeT(1727646604.into());
    let s = "2024-09-29T21:50:04Z";
    let j = format!(r#"{{"t":{s:?}}}"#);
    let o = O { t };

    assert_eq!(t.to_string(), s);
    assert_eq!(serde_json::to_string(&o)?, j);

    Ok(())
}