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
136////////////////////////////////////////////////////////////////////////////////
137// Timed Value
138////////////////////////////////////////////////////////////////////////////////
139
140/// An enum representing a value that is associated with a time validity status.
141///
142/// `TimedValue` can be used to indicate whether a value is valid, expired, or absent (none).
143#[derive(Debug, Copy, Clone, PartialEq, Eq)]
144#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
145pub enum TimedValue<T> {
146    /// value of type `T` that is considered valid
147    Valid(T),
148    /// value of type `T` that has expired and is no longer considered valid
149    Expired(T),
150    /// Indicates the absence of a value.
151    None,
152}
153
154impl<T> TimedValue<T> {
155    /// Returns `true` if the [`TimedValue`]` is `Valid`.
156    #[inline]
157    pub const fn is_valid(&self) -> bool {
158        match self {
159            TimedValue::Valid(_) => true,
160            TimedValue::Expired(_) => false,
161            TimedValue::None => false,
162        }
163    }
164
165    /// Returns `true` if the TimedValue is `Expired`.
166    #[inline]
167    pub const fn is_expired(&self) -> bool {
168        match self {
169            TimedValue::Valid(_) => false,
170            TimedValue::Expired(_) => true,
171            TimedValue::None => false,
172        }
173    }
174
175    /// Returns `true` if the TimedValue is `None`.
176    #[inline]
177    pub const fn is_none(&self) -> bool {
178        match self {
179            TimedValue::Valid(_) => false,
180            TimedValue::Expired(_) => false,
181            TimedValue::None => true,
182        }
183    }
184
185    /// Returns `true` if the TimedValue is `Valid` or `Expired`.
186    #[inline]
187    pub const fn has_value(&self) -> bool {
188        match self {
189            TimedValue::Valid(_) => true,
190            TimedValue::Expired(_) => true,
191            TimedValue::None => false,
192        }
193    }
194
195    /// Converts from `&TimedValue<T>` to `TimedValue<&T>`.
196    #[inline]
197    pub const fn as_ref(&self) -> TimedValue<&T> {
198        match *self {
199            TimedValue::Valid(ref inner) => TimedValue::Valid(inner),
200            TimedValue::Expired(ref inner) => TimedValue::Expired(inner),
201            TimedValue::None => TimedValue::None,
202        }
203    }
204}
205
206////////////////////////////////////////////////////////////////////////////////
207// Conversion
208////////////////////////////////////////////////////////////////////////////////
209
210impl<T, B> From<TimedOption<T, B>> for Option<T>
211where
212    B: TtlBackend,
213{
214    #[inline]
215    fn from(value: TimedOption<T, B>) -> Self {
216        value.into_option()
217    }
218}
219
220impl<T, B> From<TimedOption<T, B>> for TimedValue<T>
221where
222    B: TtlBackend,
223{
224    #[inline]
225    fn from(value: TimedOption<T, B>) -> Self {
226        value.into_timed_value()
227    }
228}
229
230////////////////////////////////////////////////////////////////////////////////
231// TTL Backent
232////////////////////////////////////////////////////////////////////////////////
233
234pub trait TtlBackend: Clone {
235    type Duration;
236
237    fn now() -> Self;
238    fn expired() -> Self;
239    fn add(self, dt: Self::Duration) -> Self;
240    fn is_valid(&self) -> bool;
241    fn is_expired(&self) -> bool;
242}
243
244impl TtlBackend for std::time::Instant {
245    type Duration = std::time::Duration;
246
247    #[inline]
248    fn now() -> Self {
249        std::time::Instant::now()
250    }
251
252    #[inline]
253    fn expired() -> Self {
254        std::time::Instant::now()
255    }
256
257    #[inline]
258    fn add(self, dt: Self::Duration) -> Self {
259        self + dt
260    }
261
262    #[inline]
263    fn is_valid(&self) -> bool {
264        *self > std::time::Instant::now()
265    }
266
267    #[inline]
268    fn is_expired(&self) -> bool {
269        *self <= std::time::Instant::now()
270    }
271}
272
273#[cfg(feature = "chrono")]
274impl TtlBackend for chrono::DateTime<chrono::Utc> {
275    type Duration = chrono::Duration;
276
277    #[inline]
278    fn now() -> Self {
279        chrono::Utc::now()
280    }
281
282    #[inline]
283    fn expired() -> Self {
284        chrono::Utc::now()
285    }
286
287    #[inline]
288    fn add(self, dt: Self::Duration) -> Self {
289        self + dt
290    }
291
292    #[inline]
293    fn is_valid(&self) -> bool {
294        *self > chrono::Utc::now()
295    }
296
297    #[inline]
298    fn is_expired(&self) -> bool {
299        *self <= chrono::Utc::now()
300    }
301}