rainier_session/
manager.rs1use std::sync::Arc;
5
6use chrono::Duration;
7use rainier_http::SameSite;
8use rainier_support::Result;
9
10use crate::session::SessionData;
11use crate::store::SessionStore;
12
13#[derive(Debug, Clone)]
15pub struct SessionConfig {
16 pub cookie: String,
18 pub path: String,
20 pub domain: Option<String>,
22 pub secure: bool,
24 pub same_site: SameSite,
26 pub lifetime: Duration,
28}
29
30impl SessionConfig {
31 pub fn cookie(mut self, name: impl Into<String>) -> Self {
33 self.cookie = name.into();
34 self
35 }
36
37 pub fn path(mut self, path: impl Into<String>) -> Self {
39 self.path = path.into();
40 self
41 }
42
43 pub fn domain(mut self, domain: impl Into<String>) -> Self {
45 self.domain = Some(domain.into());
46 self
47 }
48
49 pub fn secure(mut self, secure: bool) -> Self {
51 self.secure = secure;
52 self
53 }
54
55 pub fn same_site(mut self, same_site: SameSite) -> Self {
57 self.same_site = same_site;
58 self
59 }
60
61 pub fn lifetime(mut self, lifetime: Duration) -> Self {
63 self.lifetime = lifetime;
64 self
65 }
66}
67
68#[derive(Clone)]
79pub struct SessionManager {
80 store: Arc<dyn SessionStore>,
81 config: SessionConfig,
82}
83
84impl SessionManager {
85 pub fn new(store: Arc<dyn SessionStore>) -> Self {
87 Self { store, config: SessionConfig::default() }
88 }
89
90 pub fn with_config(store: Arc<dyn SessionStore>, config: SessionConfig) -> Self {
92 Self { store, config }
93 }
94
95 pub fn configure(mut self, adjust: impl FnOnce(SessionConfig) -> SessionConfig) -> Self {
97 self.config = adjust(self.config);
98 self
99 }
100
101 pub fn store(&self) -> &Arc<dyn SessionStore> {
103 &self.store
104 }
105
106 pub fn config(&self) -> &SessionConfig {
108 &self.config
109 }
110
111 pub fn driver(&self) -> &str {
113 self.store.name()
114 }
115
116 pub async fn read(&self, id: &str) -> Result<Option<SessionData>> {
120 self.store.read(id).await
121 }
122
123 pub async fn destroy(&self, id: &str) -> Result<()> {
128 self.store.destroy(id).await
129 }
130
131 pub async fn gc(&self) -> Result<u64> {
133 self.store.gc().await
134 }
135}
136
137impl std::fmt::Debug for SessionManager {
138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 f.debug_struct("SessionManager")
140 .field("driver", &self.store.name())
141 .field("cookie", &self.config.cookie)
142 .field("lifetime", &self.config.lifetime)
143 .finish()
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::store::MemorySessionStore;
151
152 fn manager() -> SessionManager {
153 SessionManager::new(Arc::new(MemorySessionStore::default()))
154 }
155
156 #[test]
157 fn the_defaults_are_the_safe_ones() {
158 let config = SessionConfig::default();
159
160 assert_eq!(config.same_site, SameSite::Lax);
161 assert_eq!(config.path, "/");
162 assert!(!config.secure, "off by default so http://localhost works");
163 }
164
165 #[test]
166 fn settings_are_configurable() {
167 let manager = manager().configure(|config| {
168 config
169 .cookie("my_app_session")
170 .secure(true)
171 .same_site(SameSite::Strict)
172 .lifetime(Duration::days(14))
173 .domain("example.com")
174 });
175
176 assert_eq!(manager.config().cookie, "my_app_session");
177 assert!(manager.config().secure);
178 assert_eq!(manager.config().same_site, SameSite::Strict);
179 assert_eq!(manager.config().lifetime, Duration::days(14));
180 assert_eq!(manager.config().domain.as_deref(), Some("example.com"));
181 }
182
183 #[tokio::test]
184 async fn a_session_can_be_read_and_destroyed_without_a_request() {
185 let manager = manager();
186 let mut values = serde_json::Map::new();
187 values.insert("user_id".into(), 42.into());
188 let data = SessionData { values, flash: Vec::new() };
189
190 manager.store().write("abc", &data).await.unwrap();
191 assert!(manager.read("abc").await.unwrap().is_some());
192
193 manager.destroy("abc").await.unwrap();
194 assert!(manager.read("abc").await.unwrap().is_none());
195 }
196
197 #[test]
198 fn the_driver_is_reported() {
199 assert_eq!(manager().driver(), "memory");
200 }
201}