aprender_present_lib/browser/
storage.rs1use serde::{de::DeserializeOwned, Serialize};
16use std::collections::HashMap;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
20pub enum StorageType {
21 #[default]
23 Local,
24 Session,
26}
27
28#[derive(Debug)]
34pub struct Storage {
35 storage_type: StorageType,
36 #[cfg(not(target_arch = "wasm32"))]
38 memory: std::sync::Mutex<HashMap<String, String>>,
39}
40
41impl Default for Storage {
42 fn default() -> Self {
43 Self::new(StorageType::Local)
44 }
45}
46
47impl Storage {
48 #[must_use]
50 pub fn new(storage_type: StorageType) -> Self {
51 Self {
52 storage_type,
53 #[cfg(not(target_arch = "wasm32"))]
54 memory: std::sync::Mutex::new(HashMap::new()),
55 }
56 }
57
58 #[must_use]
60 pub fn local() -> Self {
61 Self::new(StorageType::Local)
62 }
63
64 #[must_use]
66 pub fn session() -> Self {
67 Self::new(StorageType::Session)
68 }
69
70 #[must_use]
72 pub const fn storage_type(&self) -> StorageType {
73 self.storage_type
74 }
75
76 #[must_use]
78 pub fn get(&self, key: &str) -> Option<String> {
79 #[cfg(target_arch = "wasm32")]
80 {
81 self.get_wasm(key)
82 }
83 #[cfg(not(target_arch = "wasm32"))]
84 {
85 self.memory.lock().ok()?.get(key).cloned()
86 }
87 }
88
89 pub fn set(&self, key: &str, value: &str) -> Result<(), StorageError> {
91 #[cfg(target_arch = "wasm32")]
92 {
93 self.set_wasm(key, value)
94 }
95 #[cfg(not(target_arch = "wasm32"))]
96 {
97 self.memory
98 .lock()
99 .map_err(|_| StorageError::AccessDenied)?
100 .insert(key.to_string(), value.to_string());
101 Ok(())
102 }
103 }
104
105 pub fn remove(&self, key: &str) -> Result<(), StorageError> {
107 #[cfg(target_arch = "wasm32")]
108 {
109 self.remove_wasm(key)
110 }
111 #[cfg(not(target_arch = "wasm32"))]
112 {
113 self.memory
114 .lock()
115 .map_err(|_| StorageError::AccessDenied)?
116 .remove(key);
117 Ok(())
118 }
119 }
120
121 pub fn clear(&self) -> Result<(), StorageError> {
123 #[cfg(target_arch = "wasm32")]
124 {
125 self.clear_wasm()
126 }
127 #[cfg(not(target_arch = "wasm32"))]
128 {
129 self.memory
130 .lock()
131 .map_err(|_| StorageError::AccessDenied)?
132 .clear();
133 Ok(())
134 }
135 }
136
137 #[must_use]
139 pub fn len(&self) -> usize {
140 #[cfg(target_arch = "wasm32")]
141 {
142 self.len_wasm()
143 }
144 #[cfg(not(target_arch = "wasm32"))]
145 {
146 self.memory.lock().map(|m| m.len()).unwrap_or(0)
147 }
148 }
149
150 #[must_use]
152 pub fn is_empty(&self) -> bool {
153 self.len() == 0
154 }
155
156 #[must_use]
158 pub fn key(&self, index: usize) -> Option<String> {
159 #[cfg(target_arch = "wasm32")]
160 {
161 self.key_wasm(index)
162 }
163 #[cfg(not(target_arch = "wasm32"))]
164 {
165 self.memory.lock().ok()?.keys().nth(index).cloned()
166 }
167 }
168
169 #[must_use]
171 pub fn keys(&self) -> Vec<String> {
172 let mut keys = Vec::new();
173 for i in 0..self.len() {
174 if let Some(key) = self.key(i) {
175 keys.push(key);
176 }
177 }
178 keys
179 }
180
181 pub fn get_json<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, StorageError> {
183 match self.get(key) {
184 Some(json) => {
185 let value = serde_json::from_str(&json)
186 .map_err(|e| StorageError::SerializationError(e.to_string()))?;
187 Ok(Some(value))
188 }
189 None => Ok(None),
190 }
191 }
192
193 pub fn set_json<T: Serialize>(&self, key: &str, value: &T) -> Result<(), StorageError> {
195 let json = serde_json::to_string(value)
196 .map_err(|e| StorageError::SerializationError(e.to_string()))?;
197 self.set(key, &json)
198 }
199
200 #[cfg(target_arch = "wasm32")]
202 fn get_storage(&self) -> Option<web_sys::Storage> {
203 let window = web_sys::window()?;
204 match self.storage_type {
205 StorageType::Local => window.local_storage().ok()?,
206 StorageType::Session => window.session_storage().ok()?,
207 }
208 }
209
210 #[cfg(target_arch = "wasm32")]
211 fn get_wasm(&self, key: &str) -> Option<String> {
212 self.get_storage()?.get_item(key).ok()?
213 }
214
215 #[cfg(target_arch = "wasm32")]
216 fn set_wasm(&self, key: &str, value: &str) -> Result<(), StorageError> {
217 self.get_storage()
218 .ok_or(StorageError::NotAvailable)?
219 .set_item(key, value)
220 .map_err(|_| StorageError::QuotaExceeded)
221 }
222
223 #[cfg(target_arch = "wasm32")]
224 fn remove_wasm(&self, key: &str) -> Result<(), StorageError> {
225 self.get_storage()
226 .ok_or(StorageError::NotAvailable)?
227 .remove_item(key)
228 .map_err(|_| StorageError::AccessDenied)
229 }
230
231 #[cfg(target_arch = "wasm32")]
232 fn clear_wasm(&self) -> Result<(), StorageError> {
233 self.get_storage()
234 .ok_or(StorageError::NotAvailable)?
235 .clear()
236 .map_err(|_| StorageError::AccessDenied)
237 }
238
239 #[cfg(target_arch = "wasm32")]
240 fn len_wasm(&self) -> usize {
241 self.get_storage()
242 .and_then(|s| s.length().ok())
243 .unwrap_or(0) as usize
244 }
245
246 #[cfg(target_arch = "wasm32")]
247 fn key_wasm(&self, index: usize) -> Option<String> {
248 self.get_storage()?.key(index as u32).ok()?
249 }
250}
251
252#[derive(Debug, Clone, PartialEq, Eq)]
254pub enum StorageError {
255 NotAvailable,
257 QuotaExceeded,
259 AccessDenied,
261 SerializationError(String),
263}
264
265impl std::fmt::Display for StorageError {
266 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267 match self {
268 Self::NotAvailable => write!(f, "storage not available"),
269 Self::QuotaExceeded => write!(f, "storage quota exceeded"),
270 Self::AccessDenied => write!(f, "storage access denied"),
271 Self::SerializationError(msg) => write!(f, "serialization error: {msg}"),
272 }
273 }
274}
275
276impl std::error::Error for StorageError {}
277
278#[derive(Debug)]
282pub struct ScopedStorage {
283 inner: Storage,
284 prefix: String,
285}
286
287impl ScopedStorage {
288 #[must_use]
290 pub fn new(storage: Storage, prefix: impl Into<String>) -> Self {
291 Self {
292 inner: storage,
293 prefix: prefix.into(),
294 }
295 }
296
297 #[must_use]
299 pub fn local(prefix: impl Into<String>) -> Self {
300 Self::new(Storage::local(), prefix)
301 }
302
303 #[must_use]
305 pub fn session(prefix: impl Into<String>) -> Self {
306 Self::new(Storage::session(), prefix)
307 }
308
309 #[must_use]
311 pub fn prefix(&self) -> &str {
312 &self.prefix
313 }
314
315 fn prefixed_key(&self, key: &str) -> String {
316 format!("{}:{}", self.prefix, key)
317 }
318
319 #[must_use]
321 pub fn get(&self, key: &str) -> Option<String> {
322 self.inner.get(&self.prefixed_key(key))
323 }
324
325 pub fn set(&self, key: &str, value: &str) -> Result<(), StorageError> {
327 self.inner.set(&self.prefixed_key(key), value)
328 }
329
330 pub fn remove(&self, key: &str) -> Result<(), StorageError> {
332 self.inner.remove(&self.prefixed_key(key))
333 }
334
335 pub fn get_json<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, StorageError> {
337 self.inner.get_json(&self.prefixed_key(key))
338 }
339
340 pub fn set_json<T: Serialize>(&self, key: &str, value: &T) -> Result<(), StorageError> {
342 self.inner.set_json(&self.prefixed_key(key), value)
343 }
344
345 pub fn clear(&self) -> Result<(), StorageError> {
347 let keys: Vec<_> = self
348 .inner
349 .keys()
350 .into_iter()
351 .filter(|k| k.starts_with(&format!("{}:", self.prefix)))
352 .collect();
353
354 for key in keys {
355 self.inner.remove(&key)?;
357 }
358 Ok(())
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365
366 #[test]
367 fn test_storage_type_default() {
368 assert_eq!(StorageType::default(), StorageType::Local);
369 }
370
371 #[test]
372 fn test_storage_new() {
373 let storage = Storage::new(StorageType::Local);
374 assert_eq!(storage.storage_type(), StorageType::Local);
375
376 let storage = Storage::new(StorageType::Session);
377 assert_eq!(storage.storage_type(), StorageType::Session);
378 }
379
380 #[test]
381 fn test_storage_local() {
382 let storage = Storage::local();
383 assert_eq!(storage.storage_type(), StorageType::Local);
384 }
385
386 #[test]
387 fn test_storage_session() {
388 let storage = Storage::session();
389 assert_eq!(storage.storage_type(), StorageType::Session);
390 }
391
392 #[test]
393 fn test_storage_set_get() {
394 let storage = Storage::local();
395 storage.set("test_key", "test_value").unwrap();
396 assert_eq!(storage.get("test_key"), Some("test_value".to_string()));
397 }
398
399 #[test]
400 fn test_storage_get_nonexistent() {
401 let storage = Storage::local();
402 assert_eq!(storage.get("nonexistent"), None);
403 }
404
405 #[test]
406 fn test_storage_remove() {
407 let storage = Storage::local();
408 storage.set("to_remove", "value").unwrap();
409 assert!(storage.get("to_remove").is_some());
410 storage.remove("to_remove").unwrap();
411 assert!(storage.get("to_remove").is_none());
412 }
413
414 #[test]
415 fn test_storage_clear() {
416 let storage = Storage::local();
417 storage.set("key1", "value1").unwrap();
418 storage.set("key2", "value2").unwrap();
419 assert!(!storage.is_empty());
420 storage.clear().unwrap();
421 assert!(storage.is_empty());
422 }
423
424 #[test]
425 fn test_storage_len() {
426 let storage = Storage::local();
427 assert_eq!(storage.len(), 0);
428 storage.set("key1", "value1").unwrap();
429 assert_eq!(storage.len(), 1);
430 storage.set("key2", "value2").unwrap();
431 assert_eq!(storage.len(), 2);
432 }
433
434 #[test]
435 fn test_storage_is_empty() {
436 let storage = Storage::local();
437 assert!(storage.is_empty());
438 storage.set("key", "value").unwrap();
439 assert!(!storage.is_empty());
440 }
441
442 #[test]
443 fn test_storage_keys() {
444 let storage = Storage::local();
445 storage.set("a", "1").unwrap();
446 storage.set("b", "2").unwrap();
447 let keys = storage.keys();
448 assert_eq!(keys.len(), 2);
449 assert!(keys.contains(&"a".to_string()));
450 assert!(keys.contains(&"b".to_string()));
451 }
452
453 #[test]
454 fn test_storage_json() {
455 #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
456 struct TestData {
457 name: String,
458 value: i32,
459 }
460
461 let storage = Storage::local();
462 let data = TestData {
463 name: "test".to_string(),
464 value: 42,
465 };
466
467 storage.set_json("json_key", &data).unwrap();
468 let loaded: Option<TestData> = storage.get_json("json_key").unwrap();
469 assert_eq!(loaded, Some(data));
470 }
471
472 #[test]
473 fn test_storage_json_nonexistent() {
474 let storage = Storage::local();
475 let result: Result<Option<String>, _> = storage.get_json("nonexistent");
476 assert_eq!(result.unwrap(), None);
477 }
478
479 #[test]
480 fn test_scoped_storage_new() {
481 let scoped = ScopedStorage::local("myapp");
482 assert_eq!(scoped.prefix(), "myapp");
483 }
484
485 #[test]
486 fn test_scoped_storage_set_get() {
487 let scoped = ScopedStorage::local("test");
488 scoped.set("key", "value").unwrap();
489 assert_eq!(scoped.get("key"), Some("value".to_string()));
490
491 }
495
496 #[test]
497 fn test_scoped_storage_isolation() {
498 let scope1 = ScopedStorage::local("scope1");
499 let scope2 = ScopedStorage::local("scope2");
500
501 scope1.set("key", "value1").unwrap();
502 scope2.set("key", "value2").unwrap();
503
504 assert_eq!(scope1.get("key"), Some("value1".to_string()));
505 assert_eq!(scope2.get("key"), Some("value2".to_string()));
506 }
507
508 #[test]
509 fn test_scoped_storage_json() {
510 let scoped = ScopedStorage::local("json_test");
511 scoped.set_json("data", &vec![1, 2, 3]).unwrap();
512 let loaded: Option<Vec<i32>> = scoped.get_json("data").unwrap();
513 assert_eq!(loaded, Some(vec![1, 2, 3]));
514 }
515
516 #[test]
517 fn test_storage_error_display() {
518 assert_eq!(
519 StorageError::NotAvailable.to_string(),
520 "storage not available"
521 );
522 assert_eq!(
523 StorageError::QuotaExceeded.to_string(),
524 "storage quota exceeded"
525 );
526 assert_eq!(
527 StorageError::AccessDenied.to_string(),
528 "storage access denied"
529 );
530 assert_eq!(
531 StorageError::SerializationError("test".to_string()).to_string(),
532 "serialization error: test"
533 );
534 }
535
536 #[test]
537 fn test_storage_default() {
538 let storage = Storage::default();
539 assert_eq!(storage.storage_type(), StorageType::Local);
540 }
541}