rainier_session/driver.rs
1//! Where session state lives — [`SessionDriver`].
2
3use rainier_support::setting_enum;
4
5setting_enum! {
6 /// Which [`SessionStore`](crate::SessionStore) to build.
7 ///
8 /// ```
9 /// use rainier_session::SessionDriver;
10 /// use rainier_support::Setting;
11 ///
12 /// assert_eq!(SessionDriver::parse("cookie").unwrap(), SessionDriver::Cookie);
13 /// assert!(SessionDriver::parse("redis").is_err(), "sessions go in the cache, not a driver of their own");
14 /// ```
15 ///
16 /// Note what is *not* here: `redis`. Sessions in Redis are the `cache`
17 /// driver pointed at Redis, which is one store to configure rather than
18 /// two, and one connection pool rather than two.
19 pub enum SessionDriver: "session driver" {
20 /// This process only. Sessions vanish on restart and are not shared
21 /// between instances, so a second instance logs everyone out.
22 ///
23 /// The default because it needs nothing, and wrong for anything with
24 /// more than one process.
25 #[default]
26 Memory = "memory",
27
28 /// A table in the application's own database.
29 ///
30 /// Shared, durable, and revocable. Needs the migration —
31 /// `DatabaseSessionStore::migrations()` — and something to call
32 /// `prune()` periodically.
33 Database = "database",
34
35 /// Whatever the cache is pointed at: Redis, a Redis Cluster, or
36 /// Memcached, all behind one port.
37 ///
38 /// The cache expires them itself, so nothing has to sweep.
39 Cache = "cache",
40
41 /// The whole session, encrypted, in the cookie.
42 ///
43 /// No server state at all — and therefore **no way to revoke a
44 /// session** short of rotating the key, and a hard size limit. Read
45 /// the docs before choosing it.
46 Cookie = "cookie",
47 }
48}
49
50impl SessionDriver {
51 /// Whether sessions survive a restart of the process that made them.
52 pub fn is_durable(&self) -> bool {
53 matches!(self, Self::Database | Self::Cookie)
54 }
55
56 /// Whether the server can end a session it has already issued.
57 ///
58 /// `false` for [`Cookie`](Self::Cookie), and that is the property that
59 /// decides whether it is usable: a logout, a password change and a stolen
60 /// cookie all need this.
61 pub fn is_revocable(&self) -> bool {
62 !matches!(self, Self::Cookie)
63 }
64
65 /// Whether every instance of the application sees the same sessions.
66 pub fn is_shared(&self) -> bool {
67 !matches!(self, Self::Memory)
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use rainier_support::Setting;
75
76 #[test]
77 fn the_cookie_driver_is_the_one_that_cannot_be_revoked() {
78 assert!(!SessionDriver::Cookie.is_revocable());
79 for driver in SessionDriver::ALL.iter().filter(|d| **d != SessionDriver::Cookie) {
80 assert!(driver.is_revocable(), "{driver} should be revocable");
81 }
82 }
83
84 #[test]
85 fn memory_is_the_only_unshared_driver() {
86 assert!(!SessionDriver::Memory.is_shared());
87 assert!(SessionDriver::Cache.is_shared());
88 assert!(SessionDriver::Database.is_shared());
89 assert!(SessionDriver::Cookie.is_shared());
90 }
91
92 #[test]
93 fn cache_sessions_are_shared_but_not_durable() {
94 // A cache is allowed to evict. That is the distinction the two
95 // predicates exist to keep apart.
96 assert!(SessionDriver::Cache.is_shared());
97 assert!(!SessionDriver::Cache.is_durable());
98 }
99}