tag2upload_service_manager/
utils.rs

1
2use crate::prelude::*;
3
4use std::time::SystemTime;
5
6define_derive_deftly! {
7    export DebugTransparent expect items:
8
9    impl Debug for $tname {
10        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
11            $(
12                Debug::fmt(&self.$fname, f)
13            )
14        }
15    }
16}
17
18pub fn handle_string_fmt_error(_: fmt::Error) -> ! {
19    panic!("failed to format onto String")
20}
21
22#[macro_export]
23macro_rules! write_string { { $($a:tt)* }=> {
24    write!($($a)*)
25        .unwrap_or_else(|e| $crate::utils::handle_string_fmt_error(e))
26} }
27
28/// Establish bindings, within a sub-expression
29///
30/// ```
31/// # use tag2upload_service_manager::with_let;
32/// # let EXPRESSION = 1;
33/// # let BODY = 2;
34/// with_let!([
35///     BINDING = EXPRESSION;
36///     // .. more BINDINGs ...
37/// ], {
38///     BODY
39/// });
40/// ```
41///
42/// Expands to a 1-arm `match` (the "`match` lifetime extension trick).
43/// Each subsequent `BINDING` may refer to previous ones.
44#[macro_export]
45macro_rules! with_let {
46    {
47        [ ],
48        { $($body:tt)* }
49    } => {
50        { $($body)* }
51    };
52    {
53        [ $bind0:ident = $val0:expr; $($bindn:tt)* ],
54        { $($body:tt)* }
55    } => {
56        match $val0 {
57            $bind0 => with_let!( [ $($bindn)* ], { $($body)* } ),
58        }
59    }
60}
61
62#[ext(WatchReceiverExt)]
63pub impl<T: Send + Sync> watch::Receiver<T> {
64    fn wait_for_then<R: Send + Sync>(
65        &mut self,
66        mut f: impl FnMut(&T) -> Option<R> + Send + Sync,
67    ) -> impl Future<Output = Result<R, watch::error::RecvError>>
68        + Send + Sync
69    {
70        async move {
71            let mut r = None;
72            self.wait_for(|rx| {
73                r = f(&rx);
74                r.is_some()
75            }).await?;
76            Ok(r.expect("watch::Receiver misbehaved"))
77        }
78    }
79}
80
81impl Globals {
82    pub fn now_systemtime(&self) -> SystemTime {
83        let now = SystemTime::now();
84
85        // We don't care about overflow in here, because the offset
86        // is always zero in production.
87        {
88            let add: i64 = self.config.testing.time_offset;
89
90            #[cfg(test)]
91            let add = add + *self.test_suppl.simulated_time_advance
92                .lock().expect("simulated time poisoned");
93
94            if add < 0 {
95                now - Duration::from_secs(-add as _)
96            } else if add > 0 {
97                now + Duration::from_secs(add as _)
98            } else {
99                now
100            }
101        }
102    }
103
104    pub fn now(&self) -> TimeT {
105        self.now_systemtime().into()
106    }
107
108    pub fn check_tag_recency(
109        &self,
110        tag_date: SystemTime,
111    ) -> Result<IsRecentEnough, NFR> {
112        let now_st = self.now_systemtime();
113
114        macro_rules! check_interval_max { {
115            $var:ident <= $max:ident else $constructor:ident
116        } => {
117            let max = self.config.intervals.$max;
118            if $var > *max {
119                return Err(NFR::$constructor {
120                    $var: $var.into(),
121                    max: (*max).into(),
122                })
123            }
124        } }
125
126        if let Ok(age) = now_st.duration_since(tag_date) {
127            check_interval_max!(age <= max_tag_age else TagTooOld);
128        } else if let Ok(skew) = tag_date.duration_since(now_st) {
129            check_interval_max!(skew <= max_tag_age_skew else TagTooNew);
130        }
131
132        Ok(IsRecentEnough::new_unchecked())
133    }
134}
135
136/// Token indicating that the tag date has been checked
137pub struct IsRecentEnough { _hidden: () }
138
139impl IsRecentEnough {
140    pub fn new_unchecked() -> Self {
141        IsRecentEnough { _hidden: () }
142    }
143}
144
145/// `Duration` which displays and parses and serialises with [`humantime`]
146///
147/// Astonishingly `humantime_serde::Duration` doesn't impl `Display`!
148#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy)]
149#[derive(Into, From, Deref)]
150pub struct HtDuration(pub Duration);
151
152impl Serialize for HtDuration {
153    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
154        humantime_serde::Serde::from(self.0).serialize(s)
155    }
156}
157impl<'de> Deserialize<'de> for HtDuration {
158    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
159        let h = humantime_serde::Serde::<Duration>::deserialize(d)?;
160        Ok(HtDuration(*h))
161    }
162}
163impl FromStr for HtDuration {
164    type Err = <humantime::Duration as FromStr>::Err;
165    fn from_str(s: &str) -> Result<Self, Self::Err> {
166        let h: humantime::Duration = s.parse()?;
167        Ok(HtDuration(h.into()))
168    }
169}
170impl Display for HtDuration {
171    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
172        let h = humantime::Duration::from(self.0);
173        Display::fmt(&h, f)
174    }
175}
176
177/// `TimeT` which displays and serialises with [`humantime`]
178#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy)]
179#[derive(Into, From, Deref)]
180pub struct HtTimeT(pub TimeT);
181
182impl Serialize for HtTimeT {
183    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
184        humantime_serde::Serde::from(SystemTime::from(self.0)).serialize(s)
185    }
186}
187impl Display for HtTimeT {
188    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
189        let h = humantime::Timestamp::from(SystemTime::from(self.0));
190        Display::fmt(&h, f)
191    }
192}
193
194pub fn unix_access(path: &str, mode: c_int) -> io::Result<()> {
195    let path: Vec<NonZeroU8> = path.as_bytes().iter()
196        .map(|c| NonZeroU8::try_from(*c))
197        .collect::<Result<_, _>>().
198        map_err(|e| io::Error::new(
199            io::ErrorKind::InvalidInput,
200            e,
201        ))?;
202
203    let path: CString = path.into();
204
205    // SAFETY: path.as_ptr() is valid - we just made it.
206    // We dispose of path later, proving it's still alive;
207    let r = unsafe { libc::access(path.as_ptr(), mode) };
208    drop::<CString>(path);
209
210    if r == 0 {
211        Ok(())
212    } else {
213        Err(io::Error::last_os_error())
214    }
215}
216
217#[test]
218fn ht_duration() -> test_prelude::TestResult<()> {
219    #[derive(Serialize, Deserialize, Debug)]
220    struct O {
221        d: HtDuration,
222    }
223
224    let s = "27m";
225    let j = format!(r#"{{"d":{s:?}}}"#);
226
227    let o: O = serde_json::from_str(&j)?;
228    let d = HtDuration::from_str(s)?;
229
230    assert_eq!(*d, Duration::from_secs(27 * 60));
231    assert_eq!(o.d, d);
232    assert_eq!(d.to_string(), s);
233    assert_eq!(serde_json::to_string(&o)?, j);
234
235    Ok(())
236}
237
238#[test]
239fn ht_time_t() -> test_prelude::TestResult<()> {
240    #[derive(Serialize, Debug)]
241    struct O {
242        t: HtTimeT,
243    }
244
245    let t = HtTimeT(1727646604.into());
246    let s = "2024-09-29T21:50:04Z";
247    let j = format!(r#"{{"t":{s:?}}}"#);
248    let o = O { t };
249
250    assert_eq!(t.to_string(), s);
251    assert_eq!(serde_json::to_string(&o)?, j);
252
253    Ok(())
254}