ssi_status/client/
cache.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use std::{
    collections::HashMap,
    hash::Hash,
    sync::Arc,
    time::{Duration, Instant},
};

use parking_lot::RwLock;

use crate::{EncodedStatusMap, StatusMap};

use super::{MaybeCached, ProviderError, StatusMapProvider, TypedStatusMapProvider};

pub struct Cached<I: ToOwned, T: EncodedStatusMap, R> {
    remote_provider: R,
    map: RwLock<HashMap<I::Owned, CacheEntry<T::Decoded>>>,
    maximum_ttl: Option<Duration>,
}

impl<I: ToOwned, T: EncodedStatusMap, R> Cached<I, T, R> {
    pub fn new(remote_provider: R, maximum_ttl: Option<Duration>) -> Self {
        Self {
            remote_provider,
            map: RwLock::new(HashMap::new()),
            maximum_ttl,
        }
    }
}

impl<I: ToOwned + Eq + Hash, T: EncodedStatusMap, R: TypedStatusMapProvider<I, T>> Cached<I, T, R>
where
    I::Owned: Eq + Hash,
{
    async fn get_entry(&self, id: &I) -> Result<CacheEntry<T::Decoded>, ProviderError> {
        {
            let map = self.map.read();
            if let Some(entry) = map.get(id) {
                if !entry.is_expired(self.maximum_ttl) {
                    return Ok(entry.clone());
                }
            }
        }

        let entry = CacheEntry {
            status_map: Arc::new(self.remote_provider.get_typed(id).await?.into_owned()),
            retrieval_date: Instant::now(),
        };

        let mut map = self.map.write();
        map.insert(id.to_owned(), entry.clone());
        Ok(entry)
    }
}

impl<I: ToOwned + Eq + Hash, T: EncodedStatusMap, R: TypedStatusMapProvider<I, T>>
    StatusMapProvider<I> for Cached<I, T, R>
where
    I::Owned: Eq + Hash,
{
}

impl<I: ToOwned + Eq + Hash, T: EncodedStatusMap, R: TypedStatusMapProvider<I, T>>
    TypedStatusMapProvider<I, T> for Cached<I, T, R>
where
    I::Owned: Eq + Hash,
{
    async fn get_typed(&self, id: &I) -> Result<MaybeCached<T::Decoded>, ProviderError> {
        Ok(MaybeCached::Cached(self.get_entry(id).await?.status_map))
    }
}

pub struct CacheEntry<T> {
    pub status_map: Arc<T>,
    pub retrieval_date: Instant,
}

impl<T: StatusMap> CacheEntry<T> {
    pub fn is_expired(&self, maximum_ttl: Option<Duration>) -> bool {
        let ttl = match (self.status_map.time_to_live(), maximum_ttl) {
            (Some(a), Some(b)) => a.min(b),
            (Some(a), None) => a,
            (None, Some(b)) => b,
            (None, None) => return false,
        };

        Instant::now().duration_since(self.retrieval_date) >= ttl
    }
}

impl<T> Clone for CacheEntry<T> {
    fn clone(&self) -> Self {
        Self {
            status_map: self.status_map.clone(),
            retrieval_date: self.retrieval_date,
        }
    }
}