timed_option/
lib.rs

1#![doc = include_str!("../README.md")]
2
3////////////////////////////////////////////////////////////////////////////////
4// Timed Option
5////////////////////////////////////////////////////////////////////////////////
6
7/// See [module level documentation][crate]
8#[derive(Debug, Copy, Clone)]
9#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
10pub struct TimedOption<T, Ttl> {
11    value: Option<T>,
12    ttl: Ttl,
13}
14
15impl<T, B> TimedOption<T, B>
16where
17    B: TtlBackend,
18{
19    /// Some value of type `T` with a ttl.
20    #[inline]
21    pub fn new(value: T, ttl: B::Duration) -> Self {
22        TimedOption {
23            value: Some(value),
24            ttl: B::now().add(ttl),
25        }
26    }
27
28    /// No value with a expired ttl
29    #[inline]
30    pub fn empty() -> Self {
31        TimedOption {
32            value: None,
33            ttl: B::expired(),
34        }
35    }
36
37    ////////////////////////////////////////////////////////////////////////////
38    // consumption + as_refs
39    ////////////////////////////////////////////////////////////////////////////
40
41    /// Returns an `Option<T>`. If the value is some but expired a `None` is returned.
42    #[inline]
43    pub fn into_option(self) -> Option<T> {
44        match self.ttl.is_valid() {
45            true => self.value,
46            false => None,
47        }
48    }
49
50    /// Returns an `Option<&T>`. If the value is some but expired a `None` is returned.
51    #[inline]
52    pub fn as_option(&self) -> Option<&T> {
53        self.as_ref().into_option()
54    }
55
56    /// Returns an `TimedValue<T>`.
57    #[inline]
58    pub fn into_timed_value(self) -> TimedValue<T> {
59        match (self.value, self.ttl.is_valid()) {
60            (Some(value), true) => TimedValue::Valid(value),
61            (Some(value), false) => TimedValue::Expired(value),
62            (None, _) => TimedValue::None,
63        }
64    }
65
66    /// Returns an `TimedValue<&T>`.
67    #[inline]
68    pub fn as_timed_value(&self) -> TimedValue<&T> {
69        self.as_ref().into_timed_value()
70    }
71
72    /// Converts from `&TimedOption<T>` to `TimedOption<&T>`.
73    #[inline]
74    pub fn as_ref(&self) -> TimedOption<&T, B> {
75        TimedOption {
76            value: self.value.as_ref(),
77            ttl: self.ttl.clone(),
78        }
79    }
80
81    ////////////////////////////////////////////////////////////////////////////
82    // mutations
83    ////////////////////////////////////////////////////////////////////////////
84
85    /// Expires the current ttl.
86    #[inline]
87    pub fn expire(&mut self) {
88        self.ttl = B::expired();
89    }
90
91    /// Sets value to [`None`].
92    #[inline]
93    pub fn clear(&mut self) {
94        self.value = None;
95    }
96
97    /// Takes the value out of the [`TimedOption`], returning an [`Option`] and
98    /// leaving a [`None`] in its place.
99    #[inline]
100    pub fn take(&mut self) -> Option<T> {
101        let value = self.value.take();
102        match self.ttl.is_valid() {
103            true => value,
104            false => None,
105        }
106    }
107
108    /// Takes the value out of the [`TimedOption`], Returning a [`TimedValue`]
109    /// and leaving a [`None`] in its place.
110    #[inline]
111    pub fn take_timed_value(&mut self) -> TimedValue<T> {
112        match (self.value.take(), self.ttl.is_valid()) {
113            (Some(value), true) => TimedValue::Valid(value),
114            (Some(value), false) => TimedValue::Expired(value),
115            (None, _) => TimedValue::None,
116        }
117    }
118
119    ////////////////////////////////////////////////////////////////////////////
120    // utility functions
121    ////////////////////////////////////////////////////////////////////////////
122
123    /// Returns `true` if the timed-option is `Some` value and has not expired.
124    #[inline]
125    pub fn is_some(&self) -> bool {
126        self.value.is_some() & self.ttl.is_valid()
127    }
128
129    /// Returns `true` if the timed-option is `None` value or it has expired.
130    #[inline]
131    pub fn is_none(&self) -> bool {
132        self.value.is_none() | self.ttl.is_expired()
133    }
134
135    #[inline]
136    pub fn ok_or<E>(self, err: E) -> Result<T, E> {
137        self.into_timed_value().ok_or(err)
138    }
139
140    #[inline]
141    pub fn ok_or_else<E>(self, err: impl FnOnce(Option<T>) -> E) -> Result<T, E> {
142        self.into_timed_value().ok_or_else(err)
143    }
144
145    #[inline]
146    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> TimedOption<U, B> {
147        TimedOption {
148            value: self.value.map(f),
149            ttl: self.ttl,
150        }
151    }
152}
153
154////////////////////////////////////////////////////////////////////////////////
155// Timed Value
156////////////////////////////////////////////////////////////////////////////////
157
158/// An enum representing a value that is associated with a time validity status.
159///
160/// `TimedValue` can be used to indicate whether a value is valid, expired, or absent (none).
161#[derive(Debug, Copy, Clone, PartialEq, Eq)]
162#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
163pub enum TimedValue<T> {
164    /// value of type `T` that is considered valid
165    Valid(T),
166    /// value of type `T` that has expired and is no longer considered valid
167    Expired(T),
168    /// Indicates the absence of a value.
169    None,
170}
171
172impl<T> TimedValue<T> {
173    /// Returns `true` if the [`TimedValue`]` is `Valid`.
174    #[inline]
175    pub const fn is_valid(&self) -> bool {
176        match self {
177            TimedValue::Valid(_) => true,
178            TimedValue::Expired(_) => false,
179            TimedValue::None => false,
180        }
181    }
182
183    /// Returns `true` if the TimedValue is `Expired`.
184    #[inline]
185    pub const fn is_expired(&self) -> bool {
186        match self {
187            TimedValue::Valid(_) => false,
188            TimedValue::Expired(_) => true,
189            TimedValue::None => false,
190        }
191    }
192
193    /// Returns `true` if the TimedValue is `None`.
194    #[inline]
195    pub const fn is_none(&self) -> bool {
196        match self {
197            TimedValue::Valid(_) => false,
198            TimedValue::Expired(_) => false,
199            TimedValue::None => true,
200        }
201    }
202
203    /// Returns `true` if the TimedValue is `Valid` or `Expired`.
204    #[inline]
205    pub const fn has_value(&self) -> bool {
206        match self {
207            TimedValue::Valid(_) => true,
208            TimedValue::Expired(_) => true,
209            TimedValue::None => false,
210        }
211    }
212
213    /// Converts from `&TimedValue<T>` to `TimedValue<&T>`.
214    #[inline]
215    pub const fn as_ref(&self) -> TimedValue<&T> {
216        match *self {
217            TimedValue::Valid(ref inner) => TimedValue::Valid(inner),
218            TimedValue::Expired(ref inner) => TimedValue::Expired(inner),
219            TimedValue::None => TimedValue::None,
220        }
221    }
222
223    #[inline]
224    pub fn ok_or<E>(self, err: E) -> Result<T, E> {
225        match self {
226            TimedValue::Valid(inner) => Ok(inner),
227            TimedValue::Expired(_) => Err(err),
228            TimedValue::None => Err(err),
229        }
230    }
231
232    #[inline]
233    pub fn ok_or_else<E>(self, err: impl FnOnce(Option<T>) -> E) -> Result<T, E> {
234        match self {
235            TimedValue::Valid(inner) => Ok(inner),
236            TimedValue::Expired(inner) => Err(err(Some(inner))),
237            TimedValue::None => Err(err(None)),
238        }
239    }
240
241    #[inline]
242    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> TimedValue<U> {
243        match self {
244            TimedValue::Valid(inner) => TimedValue::Valid(f(inner)),
245            TimedValue::Expired(inner) => TimedValue::Expired(f(inner)),
246            TimedValue::None => TimedValue::None,
247        }
248    }
249}
250
251////////////////////////////////////////////////////////////////////////////////
252// Conversion
253////////////////////////////////////////////////////////////////////////////////
254
255impl<T, B> From<TimedOption<T, B>> for Option<T>
256where
257    B: TtlBackend,
258{
259    #[inline]
260    fn from(value: TimedOption<T, B>) -> Self {
261        value.into_option()
262    }
263}
264
265impl<T, B> From<TimedOption<T, B>> for TimedValue<T>
266where
267    B: TtlBackend,
268{
269    #[inline]
270    fn from(value: TimedOption<T, B>) -> Self {
271        value.into_timed_value()
272    }
273}
274
275////////////////////////////////////////////////////////////////////////////////
276// TTL Backent
277////////////////////////////////////////////////////////////////////////////////
278
279pub trait TtlBackend: Clone {
280    type Duration;
281
282    fn now() -> Self;
283    fn expired() -> Self;
284    fn add(self, dt: Self::Duration) -> Self;
285    fn is_valid(&self) -> bool;
286    fn is_expired(&self) -> bool;
287}
288
289impl TtlBackend for std::time::Instant {
290    type Duration = std::time::Duration;
291
292    #[inline]
293    fn now() -> Self {
294        std::time::Instant::now()
295    }
296
297    #[inline]
298    fn expired() -> Self {
299        std::time::Instant::now()
300    }
301
302    #[inline]
303    fn add(self, dt: Self::Duration) -> Self {
304        self + dt
305    }
306
307    #[inline]
308    fn is_valid(&self) -> bool {
309        *self > std::time::Instant::now()
310    }
311
312    #[inline]
313    fn is_expired(&self) -> bool {
314        *self <= std::time::Instant::now()
315    }
316}
317
318#[cfg(feature = "chrono")]
319impl TtlBackend for chrono::DateTime<chrono::Utc> {
320    type Duration = chrono::Duration;
321
322    #[inline]
323    fn now() -> Self {
324        chrono::Utc::now()
325    }
326
327    #[inline]
328    fn expired() -> Self {
329        chrono::Utc::now()
330    }
331
332    #[inline]
333    fn add(self, dt: Self::Duration) -> Self {
334        self + dt
335    }
336
337    #[inline]
338    fn is_valid(&self) -> bool {
339        *self > chrono::Utc::now()
340    }
341
342    #[inline]
343    fn is_expired(&self) -> bool {
344        *self <= chrono::Utc::now()
345    }
346}