Skip to main content

memcache/
expiration.rs

1use std::time::Duration;
2
3/// Expiration policy for a stored item.
4///
5/// memcached interprets the on-wire expiry field as follows:
6/// `0` means *never expire*; a value up to `60 * 60 * 24 * 30` (30 days) is a
7/// relative number of seconds; any larger value is an absolute unix timestamp.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum Expiration {
10    /// Never expire (wire value `0`).
11    #[default]
12    Never,
13    /// Expire after this many seconds from now.
14    Seconds(u32),
15    /// Expire at this absolute unix timestamp (seconds).
16    Unix(u32),
17}
18
19/// The largest value memcached still treats as a *relative* number of seconds.
20const RELATIVE_MAX_SECONDS: u32 = 60 * 60 * 24 * 30;
21
22impl Expiration {
23    /// The raw value written to the protocol's expiry field.
24    pub fn to_wire(self) -> u32 {
25        match self {
26            Expiration::Never => 0,
27            Expiration::Seconds(secs) => secs,
28            Expiration::Unix(ts) => ts,
29        }
30    }
31}
32
33impl From<u32> for Expiration {
34    /// A number of seconds. `0` maps to [`Expiration::Never`]; values greater
35    /// than 30 days are passed through unchanged (treated as unix timestamps by
36    /// the server).
37    fn from(secs: u32) -> Self {
38        if secs == 0 {
39            Expiration::Never
40        } else if secs <= RELATIVE_MAX_SECONDS {
41            Expiration::Seconds(secs)
42        } else {
43            Expiration::Unix(secs)
44        }
45    }
46}
47
48impl From<Duration> for Expiration {
49    fn from(duration: Duration) -> Self {
50        Expiration::from(duration.as_secs().min(u32::MAX as u64) as u32)
51    }
52}
53
54impl From<Option<Duration>> for Expiration {
55    fn from(duration: Option<Duration>) -> Self {
56        duration.map_or(Expiration::Never, Expiration::from)
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn zero_seconds_is_never() {
66        assert_eq!(Expiration::from(0u32), Expiration::Never);
67        assert_eq!(Expiration::from(0u32).to_wire(), 0);
68    }
69
70    #[test]
71    fn relative_and_absolute_boundary() {
72        assert_eq!(Expiration::from(60u32), Expiration::Seconds(60));
73        let over = RELATIVE_MAX_SECONDS + 1;
74        assert_eq!(Expiration::from(over), Expiration::Unix(over));
75    }
76
77    #[test]
78    fn duration_converts_to_seconds() {
79        assert_eq!(
80            Expiration::from(Duration::from_secs(90)),
81            Expiration::Seconds(90)
82        );
83    }
84}