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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! An arbitrary store which houses the session data.

use async_trait::async_trait;

use crate::session::{Session, SessionId, SessionRecord};

/// An arbitrary store which houses the session data.
#[async_trait]
pub trait SessionStore: Clone + Send + Sync + 'static {
    /// An error that occurs when interacting with the store.
    type Error: std::error::Error + Send + Sync;

    /// A method for saving a session in a store.
    async fn save(&self, session_record: &SessionRecord) -> Result<(), Self::Error>;

    /// A method for loading a session from a store.
    async fn load(&self, session_id: &SessionId) -> Result<Option<Session>, Self::Error>;

    /// A method for deleting a session from a store.
    async fn delete(&self, session_id: &SessionId) -> Result<(), Self::Error>;
}

/// An enumeration of both `SessionStore` error types.
#[derive(thiserror::Error, Debug)]
pub enum CachingStoreError<Cache: SessionStore, Store: SessionStore> {
    /// A cache-related error.
    #[error(transparent)]
    Cache(Cache::Error),

    /// A store-related error.
    #[error(transparent)]
    Store(Store::Error),
}

/// A session store for layered caching.
///
/// Contains both a cache, which acts as a frontend, and a store which acts as a
/// backend. Both cache and store implement `SessionStore`.
///
/// By using a cache, the cost of reads can be greatly reduced as once cached,
/// reads need only interact with the frontend, forgoing the cost of retrieving
/// the session record from the backend.
///
/// # Examples
///
/// ```rust
/// # #[cfg(all(feature = "moka_store", feature = "sqlite_store"))]
/// # {
/// # tokio_test::block_on(async {
/// use tower_sessions::{CachingSessionStore, MokaStore, SqlitePool, SqliteStore};
/// let pool = SqlitePool::connect("sqlite::memory:").await?;
/// let sqlite_store = SqliteStore::new(pool);
/// let moka_store = MokaStore::new(Some(2_000));
/// let caching_store = CachingSessionStore::new(moka_store, sqlite_store);
/// # })
/// # }
/// ```
#[derive(Debug, Clone)]
pub struct CachingSessionStore<Cache: SessionStore, Store: SessionStore> {
    cache: Cache,
    store: Store,
}

impl<Cache: SessionStore, Store: SessionStore> CachingSessionStore<Cache, Store> {
    /// Create a new `CachingSessionStore`.
    pub fn new(cache: Cache, store: Store) -> Self {
        Self { cache, store }
    }
}

#[async_trait]
impl<Cache, Store> SessionStore for CachingSessionStore<Cache, Store>
where
    Cache: SessionStore + std::fmt::Debug, // TODO: Why is this required to be Debug?
    Store: SessionStore + std::fmt::Debug,
{
    type Error = CachingStoreError<Cache, Store>;

    async fn save(&self, session_record: &SessionRecord) -> Result<(), Self::Error> {
        self.store
            .save(session_record)
            .await
            .map_err(Self::Error::Store)?;
        self.cache
            .save(session_record)
            .await
            .map_err(Self::Error::Cache)?;

        Ok(())
    }

    async fn load(&self, session_id: &SessionId) -> Result<Option<Session>, Self::Error> {
        match self.cache.load(session_id).await {
            // We found a session in the cache, so let's use it.
            Ok(Some(session)) => Ok(Some(session).filter(|s| !s.is_empty())),

            // We didn't find a session in the cache, so we'll try loading from the backend.
            //
            // When we find a session in the backend, we'll hydrate our cache with it.
            Ok(None) => {
                let session = self
                    .store
                    .load(session_id)
                    .await
                    .map_err(Self::Error::Store)?;

                if let Some(ref session) = session {
                    let session_record = session.into();
                    self.cache
                        .save(&session_record)
                        .await
                        .map_err(Self::Error::Cache)?;
                } else {
                    // If we know the session doesn't exist in the store, we cache the negative
                    // lookup to avoid future roundtrips to the store.
                    let tombstone = SessionRecord::tombstone_from_id(*session_id);
                    self.cache
                        .save(&tombstone)
                        .await
                        .map_err(Self::Error::Cache)?;
                }

                Ok(session)
            }

            // Some error occurred with our cache so we'll bubble this up.
            Err(err) => Err(Self::Error::Cache(err)),
        }
    }

    async fn delete(&self, session_id: &SessionId) -> Result<(), Self::Error> {
        self.store
            .delete(session_id)
            .await
            .map_err(Self::Error::Store)?;
        self.cache
            .delete(session_id)
            .await
            .map_err(Self::Error::Cache)?;
        Ok(())
    }
}