oxihuman_core/
session_store.rs1#![allow(dead_code)]
4
5use std::collections::HashMap;
8
9#[allow(dead_code)]
11#[derive(Debug, Clone)]
12pub struct Session {
13 pub id: String,
14 pub data: HashMap<String, String>,
15 pub created_at: u64,
16 pub expires_at: u64,
17}
18
19#[allow(dead_code)]
20impl Session {
21 pub fn new(id: &str, now: u64, ttl: u64) -> Self {
22 Self {
23 id: id.to_string(),
24 data: HashMap::new(),
25 created_at: now,
26 expires_at: now + ttl,
27 }
28 }
29
30 pub fn is_expired(&self, now: u64) -> bool {
31 now >= self.expires_at
32 }
33
34 pub fn set(&mut self, key: &str, value: &str) {
35 self.data.insert(key.to_string(), value.to_string());
36 }
37
38 pub fn get(&self, key: &str) -> Option<&str> {
39 self.data.get(key).map(|s| s.as_str())
40 }
41
42 pub fn remove(&mut self, key: &str) -> bool {
43 self.data.remove(key).is_some()
44 }
45
46 pub fn entry_count(&self) -> usize {
47 self.data.len()
48 }
49}
50
51#[allow(dead_code)]
53pub struct SessionStore {
54 sessions: HashMap<String, Session>,
55 now: u64,
56 default_ttl: u64,
57 evict_count: u64,
58}
59
60#[allow(dead_code)]
61impl SessionStore {
62 pub fn new(default_ttl: u64) -> Self {
63 Self {
64 sessions: HashMap::new(),
65 now: 0,
66 default_ttl,
67 evict_count: 0,
68 }
69 }
70
71 pub fn advance_time(&mut self, dt: u64) {
72 self.now += dt;
73 }
74
75 pub fn create_session(&mut self, id: &str) -> bool {
76 if self.sessions.contains_key(id) {
77 return false;
78 }
79 let s = Session::new(id, self.now, self.default_ttl);
80 self.sessions.insert(id.to_string(), s);
81 true
82 }
83
84 pub fn destroy_session(&mut self, id: &str) -> bool {
85 self.sessions.remove(id).is_some()
86 }
87
88 pub fn set(&mut self, session_id: &str, key: &str, value: &str) -> bool {
89 if let Some(s) = self.sessions.get_mut(session_id) {
90 if !s.is_expired(self.now) {
91 s.set(key, value);
92 return true;
93 }
94 }
95 false
96 }
97
98 pub fn get(&self, session_id: &str, key: &str) -> Option<&str> {
99 self.sessions
100 .get(session_id)
101 .filter(|s| !s.is_expired(self.now))
102 .and_then(|s| s.get(key))
103 }
104
105 pub fn evict_expired(&mut self) -> usize {
106 let before = self.sessions.len();
107 let now = self.now;
108 self.sessions.retain(|_, s| !s.is_expired(now));
109 let evicted = before - self.sessions.len();
110 self.evict_count += evicted as u64;
111 evicted
112 }
113
114 pub fn session_count(&self) -> usize {
115 self.sessions.len()
116 }
117
118 pub fn evict_count(&self) -> u64 {
119 self.evict_count
120 }
121
122 pub fn has_session(&self, id: &str) -> bool {
123 self.sessions.contains_key(id)
124 }
125
126 pub fn now(&self) -> u64 {
127 self.now
128 }
129
130 pub fn clear(&mut self) {
131 self.sessions.clear();
132 }
133}
134
135impl Default for SessionStore {
136 fn default() -> Self {
137 Self::new(3600)
138 }
139}
140
141pub fn new_session_store(default_ttl: u64) -> SessionStore {
142 SessionStore::new(default_ttl)
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn create_and_set() {
151 let mut store = new_session_store(1000);
152 store.create_session("sess1");
153 assert!(store.set("sess1", "user", "alice"));
154 assert_eq!(store.get("sess1", "user"), Some("alice"));
155 }
156
157 #[test]
158 fn expired_session_invisible() {
159 let mut store = new_session_store(10);
160 store.create_session("s");
161 store.set("s", "k", "v");
162 store.advance_time(20);
163 assert!(store.get("s", "k").is_none());
164 }
165
166 #[test]
167 fn evict_removes_expired() {
168 let mut store = new_session_store(5);
169 store.create_session("a");
170 store.advance_time(10);
171 store.evict_expired();
172 assert!(!store.has_session("a"));
173 }
174
175 #[test]
176 fn duplicate_session_rejected() {
177 let mut store = new_session_store(100);
178 assert!(store.create_session("x"));
179 assert!(!store.create_session("x"));
180 }
181
182 #[test]
183 fn destroy_session() {
184 let mut store = new_session_store(100);
185 store.create_session("s");
186 assert!(store.destroy_session("s"));
187 assert!(!store.has_session("s"));
188 }
189
190 #[test]
191 fn session_count() {
192 let mut store = new_session_store(100);
193 store.create_session("a");
194 store.create_session("b");
195 assert_eq!(store.session_count(), 2);
196 }
197
198 #[test]
199 fn evict_count_tracked() {
200 let mut store = new_session_store(1);
201 store.create_session("x");
202 store.advance_time(5);
203 store.evict_expired();
204 assert_eq!(store.evict_count(), 1);
205 }
206
207 #[test]
208 fn clear_all() {
209 let mut store = new_session_store(100);
210 store.create_session("a");
211 store.clear();
212 assert_eq!(store.session_count(), 0);
213 }
214
215 #[test]
216 fn get_missing_key() {
217 let mut store = new_session_store(100);
218 store.create_session("s");
219 assert!(store.get("s", "nope").is_none());
220 }
221
222 #[test]
223 fn advance_time_tracked() {
224 let mut store = new_session_store(100);
225 store.advance_time(50);
226 assert_eq!(store.now(), 50);
227 }
228}