timed_map/
map.rs

1use super::*;
2
3use crate::iter::{GenericMapIntoIter, GenericMapIter, GenericMapIterMut};
4
5macro_rules! cfg_std_feature {
6    ($($item:item)*) => {
7        $(
8            #[cfg(feature = "std")]
9            $item
10        )*
11    };
12}
13
14macro_rules! cfg_not_std_feature {
15    ($($item:item)*) => {
16        $(
17            #[cfg(not(feature = "std"))]
18            $item
19        )*
20    };
21}
22
23cfg_not_std_feature! {
24    /// Generic trait for `no_std` keys that is gated by the `std` feature
25    /// and handled at compile time.
26    pub trait GenericKey: Clone + Eq + Ord {}
27    impl<T: Clone + Eq + Ord> GenericKey for T {}
28}
29
30cfg_std_feature! {
31    /// Generic trait for `std` keys that is gated by the `std` feature
32    /// and handled at compile time.
33    pub trait GenericKey: Clone + Eq + Ord + Hash {}
34    impl<T: Clone + Eq + Ord + Hash> GenericKey for T {}
35}
36
37/// Wraps different map implementations and provides a single interface to access them.
38#[allow(clippy::enum_variant_names)]
39#[derive(Clone, Debug)]
40enum GenericMap<K, V> {
41    BTreeMap(BTreeMap<K, V>),
42    #[cfg(feature = "std")]
43    HashMap(HashMap<K, V>),
44    #[cfg(all(feature = "std", feature = "rustc-hash"))]
45    FxHashMap(FxHashMap<K, V>),
46}
47
48impl<K, V> Default for GenericMap<K, V> {
49    fn default() -> Self {
50        Self::BTreeMap(BTreeMap::default())
51    }
52}
53
54impl<K, V> GenericMap<K, V>
55where
56    K: GenericKey,
57{
58    #[inline(always)]
59    fn get(&self, k: &K) -> Option<&V> {
60        match self {
61            Self::BTreeMap(inner) => inner.get(k),
62            #[cfg(feature = "std")]
63            Self::HashMap(inner) => inner.get(k),
64            #[cfg(all(feature = "std", feature = "rustc-hash"))]
65            Self::FxHashMap(inner) => inner.get(k),
66        }
67    }
68
69    #[inline(always)]
70    fn get_mut(&mut self, k: &K) -> Option<&mut V> {
71        match self {
72            Self::BTreeMap(inner) => inner.get_mut(k),
73            #[cfg(feature = "std")]
74            Self::HashMap(inner) => inner.get_mut(k),
75            #[cfg(all(feature = "std", feature = "rustc-hash"))]
76            Self::FxHashMap(inner) => inner.get_mut(k),
77        }
78    }
79
80    #[inline(always)]
81    fn len(&self) -> usize {
82        match self {
83            Self::BTreeMap(inner) => inner.len(),
84            #[cfg(feature = "std")]
85            Self::HashMap(inner) => inner.len(),
86            #[cfg(all(feature = "std", feature = "rustc-hash"))]
87            Self::FxHashMap(inner) => inner.len(),
88        }
89    }
90
91    #[inline(always)]
92    fn keys(&self) -> Vec<K> {
93        match self {
94            Self::BTreeMap(inner) => inner.keys().cloned().collect(),
95            #[cfg(feature = "std")]
96            Self::HashMap(inner) => inner.keys().cloned().collect(),
97            #[cfg(all(feature = "std", feature = "rustc-hash"))]
98            Self::FxHashMap(inner) => inner.keys().cloned().collect(),
99        }
100    }
101
102    #[inline(always)]
103    fn is_empty(&self) -> bool {
104        match self {
105            Self::BTreeMap(inner) => inner.is_empty(),
106            #[cfg(feature = "std")]
107            Self::HashMap(inner) => inner.is_empty(),
108            #[cfg(all(feature = "std", feature = "rustc-hash"))]
109            Self::FxHashMap(inner) => inner.is_empty(),
110        }
111    }
112
113    #[inline(always)]
114    fn insert(&mut self, k: K, v: V) -> Option<V> {
115        match self {
116            Self::BTreeMap(inner) => inner.insert(k, v),
117            #[cfg(feature = "std")]
118            Self::HashMap(inner) => inner.insert(k, v),
119            #[cfg(all(feature = "std", feature = "rustc-hash"))]
120            Self::FxHashMap(inner) => inner.insert(k, v),
121        }
122    }
123
124    #[inline(always)]
125    fn clear(&mut self) {
126        match self {
127            Self::BTreeMap(inner) => inner.clear(),
128            #[cfg(feature = "std")]
129            Self::HashMap(inner) => inner.clear(),
130            #[cfg(all(feature = "std", feature = "rustc-hash"))]
131            Self::FxHashMap(inner) => inner.clear(),
132        }
133    }
134
135    #[inline(always)]
136    fn remove(&mut self, k: &K) -> Option<V> {
137        match self {
138            Self::BTreeMap(inner) => inner.remove(k),
139            #[cfg(feature = "std")]
140            Self::HashMap(inner) => inner.remove(k),
141            #[cfg(all(feature = "std", feature = "rustc-hash"))]
142            Self::FxHashMap(inner) => inner.remove(k),
143        }
144    }
145
146    fn iter(&self) -> GenericMapIter<K, V> {
147        match self {
148            Self::BTreeMap(inner) => GenericMapIter::BTreeMap(inner.iter()),
149            #[cfg(feature = "std")]
150            Self::HashMap(inner) => GenericMapIter::HashMap(inner.iter()),
151            #[cfg(all(feature = "std", feature = "rustc-hash"))]
152            Self::FxHashMap(inner) => GenericMapIter::FxHashMap(inner.iter()),
153        }
154    }
155
156    fn into_iter(self) -> GenericMapIntoIter<K, V> {
157        match self {
158            Self::BTreeMap(inner) => GenericMapIntoIter::BTreeMap(inner.into_iter()),
159            #[cfg(feature = "std")]
160            Self::HashMap(inner) => GenericMapIntoIter::HashMap(inner.into_iter()),
161            #[cfg(all(feature = "std", feature = "rustc-hash"))]
162            Self::FxHashMap(inner) => GenericMapIntoIter::FxHashMap(inner.into_iter()),
163        }
164    }
165
166    fn iter_mut(&mut self) -> GenericMapIterMut<K, V> {
167        match self {
168            Self::BTreeMap(inner) => GenericMapIterMut::BTreeMap(inner.iter_mut()),
169            #[cfg(feature = "std")]
170            Self::HashMap(inner) => GenericMapIterMut::HashMap(inner.iter_mut()),
171            #[cfg(all(feature = "std", feature = "rustc-hash"))]
172            Self::FxHashMap(inner) => GenericMapIterMut::FxHashMap(inner.iter_mut()),
173        }
174    }
175}
176
177/// Specifies the inner map implementation for `TimedMap`.
178#[cfg(feature = "std")]
179#[allow(clippy::enum_variant_names)]
180pub enum MapKind {
181    BTreeMap,
182    HashMap,
183    #[cfg(feature = "rustc-hash")]
184    FxHashMap,
185}
186
187/// Associates keys of type `K` with values of type `V`. Each entry may optionally expire after a
188/// specified duration.
189///
190/// Mutable functions automatically clears expired entries when called.
191///
192/// If no expiration is set, the entry remains constant.
193#[derive(Clone, Debug)]
194pub struct TimedMap<K, V, #[cfg(feature = "std")] C = StdClock, #[cfg(not(feature = "std"))] C> {
195    clock: C,
196
197    map: GenericMap<K, ExpirableEntry<V>>,
198    expiries: BTreeMap<u64, BTreeSet<K>>,
199
200    expiration_tick: u16,
201    expiration_tick_cap: u16,
202}
203
204#[cfg(feature = "serde")]
205impl<K: serde::Serialize + Ord, V: serde::Serialize, C: Clock> serde::Serialize
206    for TimedMap<K, V, C>
207{
208    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
209    where
210        S: serde::Serializer,
211    {
212        let now = self.clock.elapsed_seconds_since_creation();
213        match &self.map {
214            GenericMap::BTreeMap(inner) => {
215                let map = inner.iter().filter(|(_k, v)| !v.is_expired(now));
216                serializer.collect_map(map)
217            }
218            #[cfg(feature = "std")]
219            GenericMap::HashMap(inner) => {
220                let map = inner.iter().filter(|(_k, v)| !v.is_expired(now));
221                serializer.collect_map(map)
222            }
223            #[cfg(all(feature = "std", feature = "rustc-hash"))]
224            GenericMap::FxHashMap(inner) => {
225                let map = inner.iter().filter(|(_k, v)| !v.is_expired(now));
226                serializer.collect_map(map)
227            }
228        }
229    }
230}
231
232impl<'a, K, V, C> IntoIterator for &'a TimedMap<K, V, C>
233where
234    K: GenericKey,
235    C: Clock,
236{
237    type Item = (&'a K, &'a V);
238    type IntoIter = IntoIter<(&'a K, &'a V)>;
239
240    fn into_iter(self) -> Self::IntoIter {
241        let now = self.clock.elapsed_seconds_since_creation();
242        let items: Vec<(&K, &V)> = self
243            .map
244            .iter()
245            .filter_map(|(k, v)| {
246                if !v.is_expired(now) {
247                    Some((k, v.value()))
248                } else {
249                    None
250                }
251            })
252            .collect();
253        items.into_iter()
254    }
255}
256
257impl<'a, K, V, C> IntoIterator for &'a mut TimedMap<K, V, C>
258where
259    K: GenericKey,
260    C: Clock,
261{
262    type Item = (&'a K, &'a mut V);
263    type IntoIter = IntoIter<(&'a K, &'a mut V)>;
264
265    fn into_iter(self) -> Self::IntoIter {
266        let now = self.clock.elapsed_seconds_since_creation();
267        let items: Vec<(&K, &mut V)> = self
268            .map
269            .iter_mut()
270            .filter_map(|(k, v)| {
271                if !v.is_expired(now) {
272                    Some((k, v.value_mut()))
273                } else {
274                    None
275                }
276            })
277            .collect();
278        items.into_iter()
279    }
280}
281
282impl<K, V, C> IntoIterator for TimedMap<K, V, C>
283where
284    K: GenericKey,
285    C: Clock,
286{
287    type Item = (K, V);
288    type IntoIter = IntoIter<(K, V)>;
289
290    fn into_iter(self) -> Self::IntoIter {
291        let now = self.clock.elapsed_seconds_since_creation();
292        let items: Vec<(K, V)> = self
293            .map
294            .into_iter()
295            .filter_map(|(k, v)| {
296                if !v.is_expired(now) {
297                    Some((k.clone(), v.owned_value()))
298                } else {
299                    None
300                }
301            })
302            .collect();
303        items.into_iter()
304    }
305}
306
307impl<K, V, C> Default for TimedMap<K, V, C>
308where
309    C: Default,
310{
311    fn default() -> Self {
312        Self {
313            clock: Default::default(),
314
315            map: GenericMap::default(),
316            expiries: BTreeMap::default(),
317
318            expiration_tick: 0,
319            expiration_tick_cap: 1,
320        }
321    }
322}
323
324#[cfg(feature = "std")]
325impl<K, V> TimedMap<K, V, StdClock>
326where
327    K: GenericKey,
328{
329    /// Creates an empty map.
330    pub fn new() -> Self {
331        Self::default()
332    }
333
334    /// Creates an empty map based on the chosen map implementation specified by `MapKind`.
335    pub fn new_with_map_kind(map_kind: MapKind) -> Self {
336        let map = match map_kind {
337            MapKind::BTreeMap => GenericMap::<K, ExpirableEntry<V>>::BTreeMap(BTreeMap::default()),
338            MapKind::HashMap => GenericMap::HashMap(HashMap::default()),
339            #[cfg(feature = "rustc-hash")]
340            MapKind::FxHashMap => GenericMap::FxHashMap(FxHashMap::default()),
341        };
342
343        Self {
344            map,
345
346            clock: StdClock::default(),
347            expiries: BTreeMap::default(),
348
349            expiration_tick: 0,
350            expiration_tick_cap: 1,
351        }
352    }
353}
354
355impl<K, V, C> TimedMap<K, V, C>
356where
357    C: Clock,
358    K: GenericKey,
359{
360    /// Creates an empty `TimedMap`.
361    ///
362    /// Uses the provided `clock` to handle expiration times.
363    #[cfg(not(feature = "std"))]
364    pub fn new(clock: C) -> Self {
365        Self {
366            clock,
367            map: GenericMap::default(),
368            expiries: BTreeMap::default(),
369            expiration_tick: 0,
370            expiration_tick_cap: 1,
371        }
372    }
373
374    /// Configures `expiration_tick_cap`, which sets how often `TimedMap::drop_expired_entries`
375    /// is automatically called. The default value is 1.
376    ///
377    /// On each insert (excluding `unchecked` ones), an internal counter `expiration_tick` is incremented.
378    /// When `expiration_tick` meets or exceeds `expiration_tick_cap`, `TimedMap::drop_expired_entries` is
379    /// triggered to remove expired entries.
380    ///
381    /// Use this to control cleanup frequency and optimize performance. For example, if your workload
382    /// involves about 100 inserts within couple seconds, setting `expiration_tick_cap` to 100 can improve
383    /// the performance significantly.
384    #[inline(always)]
385    pub fn expiration_tick_cap(mut self, expiration_tick_cap: u16) -> Self {
386        self.expiration_tick_cap = expiration_tick_cap;
387        self
388    }
389
390    /// Returns the associated value if present and not expired.
391    ///
392    /// To retrieve the value without checking expiration, use `TimedMap::get_unchecked`.
393    pub fn get(&self, k: &K) -> Option<&V> {
394        self.map
395            .get(k)
396            .filter(|v| !v.is_expired(self.clock.elapsed_seconds_since_creation()))
397            .map(|v| v.value())
398    }
399
400    /// Returns a mutable reference to the value corresponding to the key.
401    ///
402    /// To retrieve the value without checking expiration, use `TimedMap::get_mut_unchecked`.
403    pub fn get_mut(&mut self, k: &K) -> Option<&mut V> {
404        self.map
405            .get_mut(k)
406            .filter(|v| !v.is_expired(self.clock.elapsed_seconds_since_creation()))
407            .map(|v| v.value_mut())
408    }
409
410    /// Returns the associated value if present, regardless of whether it is expired.
411    ///
412    /// If you only want non-expired entries, use `TimedMap::get` instead.
413    #[inline(always)]
414    pub fn get_unchecked(&self, k: &K) -> Option<&V> {
415        self.map.get(k).map(|v| v.value())
416    }
417
418    /// Returns a mutable reference to the associated value if present, regardless of
419    /// whether it is expired.
420    ///
421    /// If you only want non-expired entries, use `TimedMap::get_mut` instead.
422    #[inline(always)]
423    pub fn get_mut_unchecked(&mut self, k: &K) -> Option<&mut V> {
424        self.map.get_mut(k).map(|v| v.value_mut())
425    }
426
427    /// Returns the associated value's `Duration` if present and not expired.
428    ///
429    /// Returns `None` if the entry does not exist or is constant.
430    pub fn get_remaining_duration(&self, k: &K) -> Option<Duration> {
431        match self.map.get(k) {
432            Some(v) => {
433                let now = self.clock.elapsed_seconds_since_creation();
434                if v.is_expired(now) {
435                    return None;
436                }
437
438                v.remaining_duration(now)
439            }
440            None => None,
441        }
442    }
443
444    /// Returns the number of unexpired elements in the map.
445    ///
446    /// See `TimedMap::len_expired` and `TimedMap::len_unchecked` for other usages.
447    #[inline(always)]
448    pub fn len(&self) -> usize {
449        self.map.len() - self.len_expired()
450    }
451
452    /// Returns the number of expired elements in the map.
453    ///
454    /// See `TimedMap::len` and `TimedMap::len_unchecked` for other usages.
455    #[inline(always)]
456    pub fn len_expired(&self) -> usize {
457        let now = self.clock.elapsed_seconds_since_creation();
458        self.expiries
459            .iter()
460            .filter_map(
461                |(exp, keys)| {
462                    if exp <= &now {
463                        Some(keys.len())
464                    } else {
465                        None
466                    }
467                },
468            )
469            .sum()
470    }
471
472    /// Returns the total number of elements (including expired ones) in the map.
473    ///
474    /// See `TimedMap::len` and `TimedMap::len_expired` for other usages.
475    #[inline(always)]
476    pub fn len_unchecked(&self) -> usize {
477        self.map.len()
478    }
479
480    /// Returns keys of the map
481    #[inline(always)]
482    pub fn keys(&self) -> Vec<K> {
483        self.map.keys()
484    }
485
486    /// Returns true if the map contains no elements.
487    #[inline(always)]
488    pub fn is_empty(&self) -> bool {
489        self.map.is_empty()
490    }
491
492    /// Inserts a key-value pair with an expiration duration. If duration is `None`,
493    /// entry will be stored in a non-expirable way.
494    ///
495    /// If a value already exists for the given key, it will be updated and then
496    /// the old one will be returned.
497    #[inline(always)]
498    fn insert(&mut self, k: K, v: V, expires_at: Option<u64>) -> Option<V> {
499        let entry = ExpirableEntry::new(v, expires_at);
500        match self.map.insert(k.clone(), entry) {
501            Some(old) => {
502                // Remove the old expiry record
503                if let EntryStatus::ExpiresAtSeconds(e) = old.status() {
504                    self.drop_key_from_expiry(e, &k)
505                }
506
507                Some(old.owned_value())
508            }
509            None => None,
510        }
511    }
512
513    /// Inserts a key-value pair with an expiration duration, and then drops the
514    /// expired entries.
515    ///
516    /// If a value already exists for the given key, it will be updated and then
517    /// the old one will be returned.
518    ///
519    /// If you don't want to the check expired entries, consider using `TimedMap::insert_expirable_unchecked`
520    /// instead.
521    pub fn insert_expirable(&mut self, k: K, v: V, duration: Duration) -> Option<V> {
522        self.expiration_tick += 1;
523
524        let now = self.clock.elapsed_seconds_since_creation();
525        if self.expiration_tick >= self.expiration_tick_cap {
526            self.drop_expired_entries_inner(now);
527            self.expiration_tick = 0;
528        }
529
530        let expires_at = now + duration.as_secs();
531
532        let res = self.insert(k.clone(), v, Some(expires_at));
533
534        self.expiries.entry(expires_at).or_default().insert(k);
535
536        res
537    }
538
539    /// Inserts a key-value pair with an expiration duration, without checking the expired
540    /// entries.
541    ///
542    /// If a value already exists for the given key, it will be updated and then
543    /// the old one will be returned.
544    ///
545    /// If you want to check the expired entries, consider using `TimedMap::insert_expirable`
546    /// instead.
547    pub fn insert_expirable_unchecked(&mut self, k: K, v: V, duration: Duration) -> Option<V> {
548        let now = self.clock.elapsed_seconds_since_creation();
549        let expires_at = now + duration.as_secs();
550
551        let res = self.insert(k.clone(), v, Some(expires_at));
552
553        self.expiries.entry(expires_at).or_default().insert(k);
554
555        res
556    }
557
558    /// Inserts a key-value pair with that doesn't expire, and then drops the
559    /// expired entries.
560    ///
561    /// If a value already exists for the given key, it will be updated and then
562    /// the old one will be returned.
563    ///
564    /// If you don't want to check the expired entries, consider using `TimedMap::insert_constant_unchecked`
565    /// instead.
566    pub fn insert_constant(&mut self, k: K, v: V) -> Option<V> {
567        self.expiration_tick += 1;
568
569        let now = self.clock.elapsed_seconds_since_creation();
570        if self.expiration_tick >= self.expiration_tick_cap {
571            self.drop_expired_entries_inner(now);
572            self.expiration_tick = 0;
573        }
574
575        self.insert(k, v, None)
576    }
577
578    /// Inserts a key-value pair with that doesn't expire without checking the expired
579    /// entries.
580    ///
581    /// If a value already exists for the given key, it will be updated and then
582    /// the old one will be returned.
583    ///
584    /// If you want to check the expired entries, consider using `TimedMap::insert_constant`
585    /// instead.
586    pub fn insert_constant_unchecked(&mut self, k: K, v: V) -> Option<V> {
587        self.expiration_tick += 1;
588        self.insert(k, v, None)
589    }
590
591    /// Removes a key-value pair from the map and returns the associated value if present
592    /// and not expired.
593    ///
594    /// If you want to retrieve the entry after removal even if it is expired, consider using
595    /// `TimedMap::remove_unchecked`.
596    #[inline(always)]
597    pub fn remove(&mut self, k: &K) -> Option<V> {
598        self.map
599            .remove(k)
600            .filter(|v| {
601                if let EntryStatus::ExpiresAtSeconds(expires_at_seconds) = v.status() {
602                    self.drop_key_from_expiry(expires_at_seconds, k);
603                }
604
605                !v.is_expired(self.clock.elapsed_seconds_since_creation())
606            })
607            .map(|v| v.owned_value())
608    }
609
610    /// Removes a key-value pair from the map and returns the associated value if present,
611    /// regardless of expiration status.
612    ///
613    /// If you only want the entry when it is not expired, consider using `TimedMap::remove`.
614    #[inline(always)]
615    pub fn remove_unchecked(&mut self, k: &K) -> Option<V> {
616        self.map
617            .remove(k)
618            .filter(|v| {
619                if let EntryStatus::ExpiresAtSeconds(expires_at_seconds) = v.status() {
620                    self.drop_key_from_expiry(expires_at_seconds, k);
621                }
622
623                true
624            })
625            .map(|v| v.owned_value())
626    }
627
628    /// Clears the map, removing all elements.
629    #[inline(always)]
630    pub fn clear(&mut self) {
631        self.map.clear()
632    }
633
634    /// Returns an iterator over non-expired key-value pairs.
635    pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
636        let now = self.clock.elapsed_seconds_since_creation();
637        self.map.iter().filter_map(move |(k, v)| {
638            if !v.is_expired(now) {
639                Some((k, v.value()))
640            } else {
641                None
642            }
643        })
644    }
645
646    /// Returns an iterator over all key-value pairs, including expired ones.
647    pub fn iter_unchecked(&self) -> impl Iterator<Item = (&K, &V)> {
648        self.map.iter().map(|(k, v)| (k, v.value()))
649    }
650
651    /// Returns a mutable iterator over non-expired key-value pairs.
652    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
653        let now = self.clock.elapsed_seconds_since_creation();
654        self.map.iter_mut().filter_map(move |(k, v)| {
655            if !v.is_expired(now) {
656                Some((k, v.value_mut()))
657            } else {
658                None
659            }
660        })
661    }
662
663    /// Returns a mutable iterator over all key-value pairs, including expired ones.
664    pub fn iter_mut_unchecked(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
665        self.map.iter_mut().map(|(k, v)| (k, v.value_mut()))
666    }
667
668    /// Updates the expiration status of an entry and returns the old status.
669    ///
670    /// If the entry does not exist, returns Err.
671    /// If the entry's old status is `EntryStatus::Constant`, returns None.
672    pub fn update_expiration_status(
673        &mut self,
674        key: K,
675        duration: Duration,
676    ) -> Result<Option<EntryStatus>, &'static str> {
677        match self.map.get_mut(&key) {
678            Some(entry) => {
679                let old_status = *entry.status();
680                let now = self.clock.elapsed_seconds_since_creation();
681                let expires_at = now + duration.as_secs();
682
683                entry.update_status(EntryStatus::ExpiresAtSeconds(expires_at));
684
685                if let EntryStatus::ExpiresAtSeconds(t) = &old_status {
686                    self.drop_key_from_expiry(t, &key);
687                }
688                self.expiries
689                    .entry(expires_at)
690                    .or_default()
691                    .insert(key.clone());
692
693                Ok(Some(old_status))
694            }
695            None => Err("entry not found"),
696        }
697    }
698
699    /// Clears expired entries from the map and returns them.
700    ///
701    /// Call this function when using `*_unchecked` inserts, as these do not
702    /// automatically clear expired entries.
703    #[inline(always)]
704    pub fn drop_expired_entries(&mut self) -> Vec<(K, V)> {
705        let now = self.clock.elapsed_seconds_since_creation();
706        self.drop_expired_entries_inner(now)
707    }
708
709    fn drop_expired_entries_inner(&mut self, now: u64) -> Vec<(K, V)> {
710        let mut expired_entries = Vec::new();
711        // Iterates through `expiries` in order and drops expired ones.
712        while let Some((exp, keys)) = self.expiries.pop_first() {
713            // It's safe to do early-break here as keys are sorted by expiration.
714            if exp > now {
715                self.expiries.insert(exp, keys);
716                break;
717            }
718
719            for key in keys {
720                if let Some(value) = self.map.remove(&key) {
721                    expired_entries.push((key, value.owned_value()));
722                }
723            }
724        }
725
726        expired_entries
727    }
728
729    fn drop_key_from_expiry(&mut self, expiry_key: &u64, map_key: &K) {
730        if let Some(list) = self.expiries.get_mut(expiry_key) {
731            list.remove(map_key);
732
733            if list.is_empty() {
734                self.expiries.remove(expiry_key);
735            }
736        }
737    }
738
739    /// Returns `true` if the map contains a non-expired value for the given key.
740    ///
741    /// To include expired entries as well, use [`TimedMap::contains_key_unchecked`].
742    #[inline(always)]
743    pub fn contains_key(&self, k: &K) -> bool {
744        self.get(k).is_some()
745    }
746
747    /// Returns `true` if the map contains a value for the given key regardless of expiration status.
748    ///
749    /// To exclude expired entries, use [`TimedMap::contains_key`] instead.
750    #[inline(always)]
751    pub fn contains_key_unchecked(&self, k: &K) -> bool {
752        self.map.keys().contains(k)
753    }
754}
755
756#[cfg(test)]
757#[cfg(not(feature = "std"))]
758mod tests {
759    use super::*;
760
761    struct MockClock {
762        current_time: u64,
763    }
764
765    impl Clock for MockClock {
766        fn elapsed_seconds_since_creation(&self) -> u64 {
767            self.current_time
768        }
769    }
770
771    #[test]
772    fn nostd_insert_and_get_constant_entry() {
773        let clock = MockClock { current_time: 1000 };
774        let mut map = TimedMap::new(clock);
775
776        map.insert_constant(1, "constant value");
777
778        assert_eq!(map.get(&1), Some(&"constant value"));
779        assert_eq!(map.get_remaining_duration(&1), None);
780    }
781
782    #[test]
783    fn nostd_insert_and_get_expirable_entry() {
784        let clock = MockClock { current_time: 1000 };
785        let mut map = TimedMap::new(clock);
786        let duration = Duration::from_secs(60);
787
788        map.insert_expirable(1, "expirable value", duration);
789
790        assert_eq!(map.get(&1), Some(&"expirable value"));
791        assert_eq!(map.get_remaining_duration(&1), Some(duration));
792    }
793
794    #[test]
795    fn nostd_expired_entry() {
796        let clock = MockClock { current_time: 1000 };
797        let mut map = TimedMap::new(clock);
798        let duration = Duration::from_secs(60);
799
800        // Insert entry that expires in 60 seconds
801        map.insert_expirable(1, "expirable value", duration);
802
803        // Simulate time passage beyond expiration
804        let clock = MockClock { current_time: 1070 };
805        map.clock = clock;
806
807        // The entry should be considered expired
808        assert_eq!(map.get(&1), None);
809        assert_eq!(map.get_remaining_duration(&1), None);
810    }
811
812    #[test]
813    fn nostd_remove_entry() {
814        let clock = MockClock { current_time: 1000 };
815        let mut map = TimedMap::new(clock);
816
817        map.insert_constant(1, "constant value");
818
819        assert_eq!(map.remove(&1), Some("constant value"));
820        assert_eq!(map.get(&1), None);
821    }
822
823    #[test]
824    fn nostd_drop_expired_entries() {
825        let clock = MockClock { current_time: 1000 };
826        let mut map = TimedMap::new(clock);
827
828        // Insert one constant and 2 expirable entries
829        map.insert_expirable(1, "expirable value1", Duration::from_secs(50));
830        map.insert_expirable(2, "expirable value2", Duration::from_secs(70));
831        map.insert_constant(3, "constant value");
832
833        // Simulate time passage beyond the expiration of the first entry
834        let clock = MockClock { current_time: 1055 };
835        map.clock = clock;
836
837        // Entry 1 should be removed and entry 2 and 3 should still exist
838        assert_eq!(map.get(&1), None);
839        assert_eq!(map.get(&2), Some(&"expirable value2"));
840        assert_eq!(map.get(&3), Some(&"constant value"));
841
842        // Simulate time passage again to expire second expirable entry
843        let clock = MockClock { current_time: 1071 };
844        map.clock = clock;
845
846        assert_eq!(map.get(&1), None);
847        assert_eq!(map.get(&2), None);
848        assert_eq!(map.get(&3), Some(&"constant value"));
849    }
850
851    #[test]
852    fn nostd_update_existing_entry() {
853        let clock = MockClock { current_time: 1000 };
854        let mut map = TimedMap::new(clock);
855
856        map.insert_constant(1, "initial value");
857        assert_eq!(map.get(&1), Some(&"initial value"));
858
859        // Update the value of the existing key and make it expirable
860        map.insert_expirable(1, "updated value", Duration::from_secs(15));
861        assert_eq!(map.get(&1), Some(&"updated value"));
862
863        // Simulate time passage and expire the updated entry
864        let clock = MockClock { current_time: 1016 };
865        map.clock = clock;
866
867        assert_eq!(map.get(&1), None);
868    }
869
870    #[test]
871    fn nostd_update_expirable_entry_status() {
872        let clock = MockClock { current_time: 1000 };
873        let mut map = TimedMap::new(clock);
874
875        map.insert_constant(1, "initial value");
876        assert_eq!(map.get(&1), Some(&"initial value"));
877
878        // Update the value of the existing key and make it expirable
879        map.update_expiration_status(1, Duration::from_secs(16))
880            .expect("entry update shouldn't fail");
881        assert_eq!(map.get(&1), Some(&"initial value"));
882
883        // Simulate time passage and expire the updated entry
884        let clock = MockClock { current_time: 1017 };
885        map.clock = clock;
886        assert_eq!(map.get(&1), None);
887    }
888
889    #[test]
890    fn nostd_update_expirable_entry_status_with_previou_time() {
891        let clock = MockClock { current_time: 1000 };
892        let mut map = TimedMap::new(clock);
893
894        // Insert map entry followed by immediately updating expiration time
895        map.insert_expirable(1, "expirable value", Duration::from_secs(15));
896        map.update_expiration_status(1, Duration::from_secs(15))
897            .expect("entry update shouldn't fail");
898
899        // We should still have our entry.
900        assert_eq!(map.get(&1), Some(&"expirable value"));
901        assert!(map.expiries.contains_key(&1015));
902    }
903
904    #[test]
905    fn nostd_contains_key() {
906        let clock = MockClock { current_time: 1000 };
907        let mut map = TimedMap::new(clock);
908
909        // Insert map entry and check if exists
910        map.insert_expirable(1, "expirable value", Duration::from_secs(5));
911        assert!(map.contains_key(&1));
912    }
913}
914
915#[cfg(feature = "std")]
916#[cfg(test)]
917mod std_tests {
918    use core::ops::Add;
919
920    use super::*;
921
922    #[test]
923    fn std_expirable_and_constant_entries() {
924        let mut map = TimedMap::new();
925
926        map.insert_constant(1, "constant value");
927        map.insert_expirable(2, "expirable value", Duration::from_secs(2));
928
929        assert_eq!(map.get(&1), Some(&"constant value"));
930        assert_eq!(map.get(&2), Some(&"expirable value"));
931
932        assert_eq!(map.get_remaining_duration(&1), None);
933        assert!(map.get_remaining_duration(&2).is_some());
934    }
935
936    #[test]
937    fn std_expired_entry_removal() {
938        let mut map = TimedMap::new();
939        let duration = Duration::from_secs(2);
940
941        map.insert_expirable(1, "expirable value", duration);
942
943        // Wait for expiration
944        std::thread::sleep(Duration::from_secs(3));
945
946        // Entry should now be expired
947        assert_eq!(map.get(&1), None);
948        assert_eq!(map.get_remaining_duration(&1), None);
949    }
950
951    #[test]
952    fn std_remove_entry() {
953        let mut map = TimedMap::new();
954
955        map.insert_constant(1, "constant value");
956        map.insert_expirable(2, "expirable value", Duration::from_secs(2));
957
958        assert_eq!(map.remove(&1), Some("constant value"));
959        assert_eq!(map.remove(&2), Some("expirable value"));
960
961        assert_eq!(map.get(&1), None);
962        assert_eq!(map.get(&2), None);
963    }
964
965    #[test]
966    fn std_drop_expired_entries() {
967        let mut map = TimedMap::new();
968
969        map.insert_expirable(1, "expirable value1", Duration::from_secs(2));
970        map.insert_expirable(2, "expirable value2", Duration::from_secs(4));
971
972        // Wait for expiration
973        std::thread::sleep(Duration::from_secs(3));
974
975        // Entry 1 should be removed and entry 2 should still exist
976        assert_eq!(map.get(&1), None);
977        assert_eq!(map.get(&2), Some(&"expirable value2"));
978    }
979
980    #[test]
981    fn std_update_existing_entry() {
982        let mut map = TimedMap::new();
983
984        map.insert_constant(1, "initial value");
985        assert_eq!(map.get(&1), Some(&"initial value"));
986
987        // Update the value of the existing key and make it expirable
988        map.insert_expirable(1, "updated value", Duration::from_secs(1));
989        assert_eq!(map.get(&1), Some(&"updated value"));
990
991        std::thread::sleep(Duration::from_secs(2));
992
993        // Should be expired now
994        assert_eq!(map.get(&1), None);
995    }
996
997    #[test]
998    fn std_insert_constant_and_expirable_combined() {
999        let mut map = TimedMap::new();
1000
1001        // Insert a constant entry and an expirable entry
1002        map.insert_constant(1, "constant value");
1003        map.insert_expirable(2, "expirable value", Duration::from_secs(2));
1004
1005        // Check both entries exist
1006        assert_eq!(map.get(&1), Some(&"constant value"));
1007        assert_eq!(map.get(&2), Some(&"expirable value"));
1008
1009        // Simulate passage of time beyond expiration
1010        std::thread::sleep(Duration::from_secs(3));
1011
1012        // Constant entry should still exist, expirable should be expired
1013        assert_eq!(map.get(&1), Some(&"constant value"));
1014        assert_eq!(map.get(&2), None);
1015    }
1016
1017    #[test]
1018    fn std_expirable_entry_still_valid_before_expiration() {
1019        let mut map = TimedMap::new();
1020
1021        // Insert an expirable entry with a duration of 3 seconds
1022        map.insert_expirable(1, "expirable value", Duration::from_secs(3));
1023
1024        // Simulate a short sleep of 2 seconds (still valid)
1025        std::thread::sleep(Duration::from_secs(2));
1026
1027        // The entry should still be valid
1028        assert_eq!(map.get(&1), Some(&"expirable value"));
1029        assert!(map.get_remaining_duration(&1).unwrap().as_secs() == 1);
1030    }
1031
1032    #[test]
1033    fn std_length_functions() {
1034        let mut map = TimedMap::new();
1035
1036        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1037        map.insert_expirable(2, "expirable value", Duration::from_secs(1));
1038        map.insert_expirable(3, "expirable value", Duration::from_secs(3));
1039        map.insert_expirable(4, "expirable value", Duration::from_secs(3));
1040        map.insert_expirable(5, "expirable value", Duration::from_secs(3));
1041        map.insert_expirable(6, "expirable value", Duration::from_secs(3));
1042
1043        std::thread::sleep(Duration::from_secs(2).add(Duration::from_millis(1)));
1044
1045        assert_eq!(map.len(), 4);
1046        assert_eq!(map.len_expired(), 2);
1047        assert_eq!(map.len_unchecked(), 6);
1048    }
1049
1050    #[test]
1051    fn std_update_expirable_entry() {
1052        let mut map = TimedMap::new();
1053
1054        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1055        map.insert_expirable(1, "expirable value", Duration::from_secs(5));
1056
1057        std::thread::sleep(Duration::from_secs(2));
1058
1059        assert!(!map.expiries.contains_key(&1));
1060        assert!(map.expiries.contains_key(&5));
1061        assert_eq!(map.get(&1), Some(&"expirable value"));
1062    }
1063
1064    #[test]
1065    fn std_update_expirable_entry_status() {
1066        let mut map = TimedMap::new();
1067
1068        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1069        map.update_expiration_status(1, Duration::from_secs(5))
1070            .expect("entry update shouldn't fail");
1071
1072        std::thread::sleep(Duration::from_secs(3));
1073        assert!(!map.expiries.contains_key(&1));
1074        assert!(map.expiries.contains_key(&5));
1075        assert_eq!(map.get(&1), Some(&"expirable value"));
1076    }
1077
1078    #[test]
1079    fn std_update_expirable_entry_status_with_previou_time() {
1080        let mut map = TimedMap::new();
1081
1082        // Insert map entry followed by immediately updating expiration time
1083        map.insert_expirable(1, "expirable value", Duration::from_secs(5));
1084        map.update_expiration_status(1, Duration::from_secs(5))
1085            .expect("entry update shouldn't fail");
1086
1087        // We should still have our entry.
1088        assert_eq!(map.get(&1), Some(&"expirable value"));
1089        assert!(map.expiries.contains_key(&5));
1090    }
1091
1092    #[test]
1093    fn std_contains_key() {
1094        let mut map = TimedMap::new();
1095
1096        // Insert map entry and check if exists
1097        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1098        assert!(map.contains_key(&1));
1099    }
1100
1101    #[test]
1102    fn std_does_not_contain_key_anymore() {
1103        let mut map = TimedMap::new();
1104
1105        // Insert map entry and check if still exists after expiry
1106        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1107        std::thread::sleep(Duration::from_secs(2));
1108        assert!(!map.contains_key(&1));
1109    }
1110
1111    #[test]
1112    fn std_contains_key_unchecked() {
1113        let mut map = TimedMap::new();
1114
1115        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1116        std::thread::sleep(Duration::from_secs(2));
1117        assert!(map.contains_key_unchecked(&1));
1118    }
1119
1120    #[test]
1121    #[cfg(feature = "serde")]
1122    fn ignore_expired_entries_on_serialize() {
1123        let mut map = TimedMap::new();
1124
1125        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1126        map.insert_expirable(2, "expirable value2", Duration::from_secs(5));
1127        std::thread::sleep(Duration::from_secs(2));
1128
1129        let actual = serde_json::json!(map);
1130        let expected = serde_json::json!({
1131            "2": "expirable value2"
1132        });
1133
1134        assert_eq!(expected, actual);
1135    }
1136}