Skip to main content

appscale_core/
storage.rs

1//! Storage Abstraction — Unified key-value, secure, and file storage API.
2//!
3//! Provides a trait-based storage layer that abstracts across platforms:
4//! - Key-value store (AsyncStorage equivalent)
5//! - Secure storage (iOS Keychain, Android Keystore, Web crypto)
6//! - File system access (sandboxed app data)
7//!
8//! Platform adapters implement `StorageBackend` for each target.
9//! The `StorageManager` coordinates across backends and provides
10//! a type-safe, thread-safe API for the rest of the engine.
11//!
12//! Design:
13//! - All operations are synchronous in the trait (platform can spawn internally)
14//! - `StorageBackend` is `Send + Sync` for safe sharing across threads
15//! - Secure storage uses a separate trait method set with explicit domain tagging
16
17use serde::{Serialize, Deserialize};
18use std::collections::HashMap;
19use std::sync::{Arc, RwLock};
20
21// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
22// Error types
23// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
24
25/// Errors from storage operations.
26#[derive(Debug, thiserror::Error)]
27pub enum StorageError {
28    #[error("Key not found: {0}")]
29    KeyNotFound(String),
30
31    #[error("Storage backend not available: {0}")]
32    BackendUnavailable(String),
33
34    #[error("Secure storage not supported on this platform")]
35    SecureNotSupported,
36
37    #[error("File not found: {0}")]
38    FileNotFound(String),
39
40    #[error("Permission denied: {0}")]
41    PermissionDenied(String),
42
43    #[error("Serialization error: {0}")]
44    SerializationError(String),
45
46    #[error("Storage quota exceeded")]
47    QuotaExceeded,
48
49    #[error("Storage I/O error: {0}")]
50    IoError(String),
51}
52
53pub type StorageResult<T> = Result<T, StorageError>;
54
55// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
56// Storage value types
57// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
58
59/// Values that can be stored in key-value storage.
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
61#[serde(untagged)]
62pub enum StorageValue {
63    Null,
64    Bool(bool),
65    Int(i64),
66    Float(f64),
67    String(String),
68    Bytes(Vec<u8>),
69    Array(Vec<StorageValue>),
70    Map(HashMap<String, StorageValue>),
71}
72
73impl StorageValue {
74    pub fn as_str(&self) -> Option<&str> {
75        match self {
76            StorageValue::String(s) => Some(s),
77            _ => None,
78        }
79    }
80
81    pub fn as_bool(&self) -> Option<bool> {
82        match self {
83            StorageValue::Bool(b) => Some(*b),
84            _ => None,
85        }
86    }
87
88    pub fn as_i64(&self) -> Option<i64> {
89        match self {
90            StorageValue::Int(i) => Some(*i),
91            _ => None,
92        }
93    }
94
95    pub fn as_f64(&self) -> Option<f64> {
96        match self {
97            StorageValue::Float(f) => Some(*f),
98            _ => None,
99        }
100    }
101
102    pub fn is_null(&self) -> bool {
103        matches!(self, StorageValue::Null)
104    }
105}
106
107impl From<String> for StorageValue {
108    fn from(s: String) -> Self { StorageValue::String(s) }
109}
110
111impl From<&str> for StorageValue {
112    fn from(s: &str) -> Self { StorageValue::String(s.to_string()) }
113}
114
115impl From<bool> for StorageValue {
116    fn from(b: bool) -> Self { StorageValue::Bool(b) }
117}
118
119impl From<i64> for StorageValue {
120    fn from(i: i64) -> Self { StorageValue::Int(i) }
121}
122
123impl From<f64> for StorageValue {
124    fn from(f: f64) -> Self { StorageValue::Float(f) }
125}
126
127// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
128// Storage namespace
129// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
130
131/// Namespace for isolating storage domains.
132/// Prevents key collisions between different parts of the app.
133#[derive(Debug, Clone, PartialEq, Eq, Hash)]
134pub struct StorageNamespace(String);
135
136impl StorageNamespace {
137    pub fn new(name: impl Into<String>) -> Self {
138        Self(name.into())
139    }
140
141    /// Default namespace for app data.
142    pub fn app_data() -> Self { Self("app_data".to_string()) }
143
144    /// Namespace for user preferences.
145    pub fn preferences() -> Self { Self("preferences".to_string()) }
146
147    /// Namespace for cache data (can be cleared by system).
148    pub fn cache() -> Self { Self("cache".to_string()) }
149
150    pub fn as_str(&self) -> &str { &self.0 }
151
152    /// Build a namespaced key: "namespace:key"
153    pub fn prefixed_key(&self, key: &str) -> String {
154        format!("{}:{}", self.0, key)
155    }
156}
157
158impl Default for StorageNamespace {
159    fn default() -> Self { Self::app_data() }
160}
161
162// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
163// Storage backend trait
164// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
165
166/// Trait that platform-specific storage implementations must implement.
167///
168/// Covers three storage tiers:
169/// 1. Key-value store (general app data)
170/// 2. Secure storage (credentials, tokens)
171/// 3. File storage (documents, media)
172pub trait StorageBackend: Send + Sync {
173    /// Platform identifier for this backend.
174    fn platform_id(&self) -> &str;
175
176    // ── Key-Value Store ──────────────────────────────────
177
178    /// Get a value by key within a namespace.
179    fn get(&self, namespace: &StorageNamespace, key: &str) -> StorageResult<Option<StorageValue>>;
180
181    /// Set a value by key within a namespace.
182    fn set(&self, namespace: &StorageNamespace, key: &str, value: StorageValue) -> StorageResult<()>;
183
184    /// Delete a key within a namespace.
185    fn delete(&self, namespace: &StorageNamespace, key: &str) -> StorageResult<()>;
186
187    /// Check if a key exists within a namespace.
188    fn contains(&self, namespace: &StorageNamespace, key: &str) -> StorageResult<bool> {
189        Ok(self.get(namespace, key)?.is_some())
190    }
191
192    /// List all keys in a namespace.
193    fn keys(&self, namespace: &StorageNamespace) -> StorageResult<Vec<String>>;
194
195    /// Clear all keys in a namespace.
196    fn clear(&self, namespace: &StorageNamespace) -> StorageResult<()>;
197
198    // ── Secure Storage ─────────────────────────────────
199
200    /// Whether this platform supports secure storage (Keychain, Keystore, etc.).
201    fn supports_secure(&self) -> bool { false }
202
203    /// Store a value securely (e.g., iOS Keychain, Android Keystore).
204    fn secure_set(&self, _key: &str, _value: &[u8]) -> StorageResult<()> {
205        Err(StorageError::SecureNotSupported)
206    }
207
208    /// Retrieve a securely stored value.
209    fn secure_get(&self, _key: &str) -> StorageResult<Option<Vec<u8>>> {
210        Err(StorageError::SecureNotSupported)
211    }
212
213    /// Delete a securely stored value.
214    fn secure_delete(&self, _key: &str) -> StorageResult<()> {
215        Err(StorageError::SecureNotSupported)
216    }
217
218    // ── File Storage ───────────────────────────────────
219
220    /// Write bytes to a file path (relative to app sandbox).
221    fn write_file(&self, path: &str, data: &[u8]) -> StorageResult<()>;
222
223    /// Read bytes from a file path (relative to app sandbox).
224    fn read_file(&self, path: &str) -> StorageResult<Vec<u8>>;
225
226    /// Delete a file.
227    fn delete_file(&self, path: &str) -> StorageResult<()>;
228
229    /// Check if a file exists.
230    fn file_exists(&self, path: &str) -> StorageResult<bool>;
231
232    /// List files in a directory (relative to app sandbox).
233    fn list_files(&self, dir: &str) -> StorageResult<Vec<String>>;
234}
235
236// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
237// Storage manager
238// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
239
240/// High-level storage coordinator.
241/// Wraps a `StorageBackend` and provides convenience methods.
242pub struct StorageManager {
243    backend: Arc<dyn StorageBackend>,
244}
245
246impl StorageManager {
247    pub fn new(backend: Arc<dyn StorageBackend>) -> Self {
248        Self { backend }
249    }
250
251    pub fn platform_id(&self) -> &str {
252        self.backend.platform_id()
253    }
254
255    // ── Key-Value convenience methods ────────────────────
256
257    /// Get a string value.
258    pub fn get_string(&self, ns: &StorageNamespace, key: &str) -> StorageResult<Option<String>> {
259        match self.backend.get(ns, key)? {
260            Some(StorageValue::String(s)) => Ok(Some(s)),
261            Some(_) => Ok(None),
262            None => Ok(None),
263        }
264    }
265
266    /// Set a string value.
267    pub fn set_string(&self, ns: &StorageNamespace, key: &str, value: &str) -> StorageResult<()> {
268        self.backend.set(ns, key, StorageValue::String(value.to_string()))
269    }
270
271    /// Get a boolean value.
272    pub fn get_bool(&self, ns: &StorageNamespace, key: &str) -> StorageResult<Option<bool>> {
273        match self.backend.get(ns, key)? {
274            Some(StorageValue::Bool(b)) => Ok(Some(b)),
275            Some(_) => Ok(None),
276            None => Ok(None),
277        }
278    }
279
280    /// Get a value, deserializing from JSON stored as a string.
281    pub fn get_json<T: serde::de::DeserializeOwned>(
282        &self, ns: &StorageNamespace, key: &str,
283    ) -> StorageResult<Option<T>> {
284        match self.backend.get(ns, key)? {
285            Some(StorageValue::String(s)) => {
286                let val = serde_json::from_str(&s)
287                    .map_err(|e| StorageError::SerializationError(e.to_string()))?;
288                Ok(Some(val))
289            }
290            _ => Ok(None),
291        }
292    }
293
294    /// Set a value, serializing to JSON and storing as a string.
295    pub fn set_json<T: Serialize>(
296        &self, ns: &StorageNamespace, key: &str, value: &T,
297    ) -> StorageResult<()> {
298        let json = serde_json::to_string(value)
299            .map_err(|e| StorageError::SerializationError(e.to_string()))?;
300        self.backend.set(ns, key, StorageValue::String(json))
301    }
302
303    /// Multi-get: fetch multiple keys at once.
304    pub fn multi_get(
305        &self, ns: &StorageNamespace, keys: &[&str],
306    ) -> StorageResult<HashMap<String, StorageValue>> {
307        let mut result = HashMap::new();
308        for &key in keys {
309            if let Some(val) = self.backend.get(ns, key)? {
310                result.insert(key.to_string(), val);
311            }
312        }
313        Ok(result)
314    }
315
316    /// Multi-set: store multiple key-value pairs at once.
317    pub fn multi_set(
318        &self, ns: &StorageNamespace, entries: &[(&str, StorageValue)],
319    ) -> StorageResult<()> {
320        for (key, value) in entries {
321            self.backend.set(ns, key, value.clone())?;
322        }
323        Ok(())
324    }
325
326    // ── Secure storage convenience ───────────────────────
327
328    pub fn supports_secure(&self) -> bool {
329        self.backend.supports_secure()
330    }
331
332    /// Store a string securely.
333    pub fn secure_set_string(&self, key: &str, value: &str) -> StorageResult<()> {
334        self.backend.secure_set(key, value.as_bytes())
335    }
336
337    /// Retrieve a securely stored string.
338    pub fn secure_get_string(&self, key: &str) -> StorageResult<Option<String>> {
339        match self.backend.secure_get(key)? {
340            Some(bytes) => {
341                let s = String::from_utf8(bytes)
342                    .map_err(|e| StorageError::SerializationError(e.to_string()))?;
343                Ok(Some(s))
344            }
345            None => Ok(None),
346        }
347    }
348
349    // ── File storage convenience ─────────────────────────
350
351    /// Write a string to a file.
352    pub fn write_text(&self, path: &str, text: &str) -> StorageResult<()> {
353        self.backend.write_file(path, text.as_bytes())
354    }
355
356    /// Read a file as a UTF-8 string.
357    pub fn read_text(&self, path: &str) -> StorageResult<String> {
358        let bytes = self.backend.read_file(path)?;
359        String::from_utf8(bytes)
360            .map_err(|e| StorageError::SerializationError(e.to_string()))
361    }
362}
363
364// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
365// In-memory backend (testing + Web fallback)
366// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
367
368/// In-memory storage backend for testing and as a Web fallback.
369/// All data is lost when the process exits.
370pub struct MemoryStorageBackend {
371    platform: String,
372    kv: RwLock<HashMap<String, StorageValue>>,
373    secure: RwLock<HashMap<String, Vec<u8>>>,
374    files: RwLock<HashMap<String, Vec<u8>>>,
375}
376
377impl MemoryStorageBackend {
378    pub fn new(platform: impl Into<String>) -> Self {
379        Self {
380            platform: platform.into(),
381            kv: RwLock::new(HashMap::new()),
382            secure: RwLock::new(HashMap::new()),
383            files: RwLock::new(HashMap::new()),
384        }
385    }
386}
387
388impl StorageBackend for MemoryStorageBackend {
389    fn platform_id(&self) -> &str { &self.platform }
390
391    fn get(&self, namespace: &StorageNamespace, key: &str) -> StorageResult<Option<StorageValue>> {
392        let full_key = namespace.prefixed_key(key);
393        let store = self.kv.read().unwrap();
394        Ok(store.get(&full_key).cloned())
395    }
396
397    fn set(&self, namespace: &StorageNamespace, key: &str, value: StorageValue) -> StorageResult<()> {
398        let full_key = namespace.prefixed_key(key);
399        let mut store = self.kv.write().unwrap();
400        store.insert(full_key, value);
401        Ok(())
402    }
403
404    fn delete(&self, namespace: &StorageNamespace, key: &str) -> StorageResult<()> {
405        let full_key = namespace.prefixed_key(key);
406        let mut store = self.kv.write().unwrap();
407        store.remove(&full_key);
408        Ok(())
409    }
410
411    fn contains(&self, namespace: &StorageNamespace, key: &str) -> StorageResult<bool> {
412        let full_key = namespace.prefixed_key(key);
413        let store = self.kv.read().unwrap();
414        Ok(store.contains_key(&full_key))
415    }
416
417    fn keys(&self, namespace: &StorageNamespace) -> StorageResult<Vec<String>> {
418        let prefix = format!("{}:", namespace.as_str());
419        let store = self.kv.read().unwrap();
420        let keys: Vec<String> = store.keys()
421            .filter(|k| k.starts_with(&prefix))
422            .map(|k| k[prefix.len()..].to_string())
423            .collect();
424        Ok(keys)
425    }
426
427    fn clear(&self, namespace: &StorageNamespace) -> StorageResult<()> {
428        let prefix = format!("{}:", namespace.as_str());
429        let mut store = self.kv.write().unwrap();
430        store.retain(|k, _| !k.starts_with(&prefix));
431        Ok(())
432    }
433
434    fn supports_secure(&self) -> bool { true }
435
436    fn secure_set(&self, key: &str, value: &[u8]) -> StorageResult<()> {
437        let mut secure = self.secure.write().unwrap();
438        secure.insert(key.to_string(), value.to_vec());
439        Ok(())
440    }
441
442    fn secure_get(&self, key: &str) -> StorageResult<Option<Vec<u8>>> {
443        let secure = self.secure.read().unwrap();
444        Ok(secure.get(key).cloned())
445    }
446
447    fn secure_delete(&self, key: &str) -> StorageResult<()> {
448        let mut secure = self.secure.write().unwrap();
449        secure.remove(key);
450        Ok(())
451    }
452
453    fn write_file(&self, path: &str, data: &[u8]) -> StorageResult<()> {
454        let mut files = self.files.write().unwrap();
455        files.insert(path.to_string(), data.to_vec());
456        Ok(())
457    }
458
459    fn read_file(&self, path: &str) -> StorageResult<Vec<u8>> {
460        let files = self.files.read().unwrap();
461        files.get(path).cloned()
462            .ok_or_else(|| StorageError::FileNotFound(path.to_string()))
463    }
464
465    fn delete_file(&self, path: &str) -> StorageResult<()> {
466        let mut files = self.files.write().unwrap();
467        files.remove(path);
468        Ok(())
469    }
470
471    fn file_exists(&self, path: &str) -> StorageResult<bool> {
472        let files = self.files.read().unwrap();
473        Ok(files.contains_key(path))
474    }
475
476    fn list_files(&self, dir: &str) -> StorageResult<Vec<String>> {
477        let prefix = if dir.ends_with('/') { dir.to_string() } else { format!("{dir}/") };
478        let files = self.files.read().unwrap();
479        let listing: Vec<String> = files.keys()
480            .filter(|k| k.starts_with(&prefix))
481            .cloned()
482            .collect();
483        Ok(listing)
484    }
485}
486
487// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
488// Platform adapter stubs
489// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
490
491/// iOS storage backend stub.
492/// Production: NSUserDefaults (KV) + Keychain (secure) + FileManager (files).
493pub struct IosStorageBackend {
494    memory: MemoryStorageBackend,
495}
496
497impl IosStorageBackend {
498    pub fn new() -> Self {
499        Self { memory: MemoryStorageBackend::new("ios") }
500    }
501}
502
503impl StorageBackend for IosStorageBackend {
504    fn platform_id(&self) -> &str { "ios" }
505    fn get(&self, ns: &StorageNamespace, key: &str) -> StorageResult<Option<StorageValue>> { self.memory.get(ns, key) }
506    fn set(&self, ns: &StorageNamespace, key: &str, value: StorageValue) -> StorageResult<()> { self.memory.set(ns, key, value) }
507    fn delete(&self, ns: &StorageNamespace, key: &str) -> StorageResult<()> { self.memory.delete(ns, key) }
508    fn keys(&self, ns: &StorageNamespace) -> StorageResult<Vec<String>> { self.memory.keys(ns) }
509    fn clear(&self, ns: &StorageNamespace) -> StorageResult<()> { self.memory.clear(ns) }
510    fn supports_secure(&self) -> bool { true } // iOS Keychain
511    fn secure_set(&self, key: &str, value: &[u8]) -> StorageResult<()> { self.memory.secure_set(key, value) }
512    fn secure_get(&self, key: &str) -> StorageResult<Option<Vec<u8>>> { self.memory.secure_get(key) }
513    fn secure_delete(&self, key: &str) -> StorageResult<()> { self.memory.secure_delete(key) }
514    fn write_file(&self, path: &str, data: &[u8]) -> StorageResult<()> { self.memory.write_file(path, data) }
515    fn read_file(&self, path: &str) -> StorageResult<Vec<u8>> { self.memory.read_file(path) }
516    fn delete_file(&self, path: &str) -> StorageResult<()> { self.memory.delete_file(path) }
517    fn file_exists(&self, path: &str) -> StorageResult<bool> { self.memory.file_exists(path) }
518    fn list_files(&self, dir: &str) -> StorageResult<Vec<String>> { self.memory.list_files(dir) }
519}
520
521/// Android storage backend stub.
522/// Production: SharedPreferences (KV) + Android Keystore (secure) + app files dir (files).
523pub struct AndroidStorageBackend {
524    memory: MemoryStorageBackend,
525}
526
527impl AndroidStorageBackend {
528    pub fn new() -> Self {
529        Self { memory: MemoryStorageBackend::new("android") }
530    }
531}
532
533impl StorageBackend for AndroidStorageBackend {
534    fn platform_id(&self) -> &str { "android" }
535    fn get(&self, ns: &StorageNamespace, key: &str) -> StorageResult<Option<StorageValue>> { self.memory.get(ns, key) }
536    fn set(&self, ns: &StorageNamespace, key: &str, value: StorageValue) -> StorageResult<()> { self.memory.set(ns, key, value) }
537    fn delete(&self, ns: &StorageNamespace, key: &str) -> StorageResult<()> { self.memory.delete(ns, key) }
538    fn keys(&self, ns: &StorageNamespace) -> StorageResult<Vec<String>> { self.memory.keys(ns) }
539    fn clear(&self, ns: &StorageNamespace) -> StorageResult<()> { self.memory.clear(ns) }
540    fn supports_secure(&self) -> bool { true } // Android Keystore
541    fn secure_set(&self, key: &str, value: &[u8]) -> StorageResult<()> { self.memory.secure_set(key, value) }
542    fn secure_get(&self, key: &str) -> StorageResult<Option<Vec<u8>>> { self.memory.secure_get(key) }
543    fn secure_delete(&self, key: &str) -> StorageResult<()> { self.memory.secure_delete(key) }
544    fn write_file(&self, path: &str, data: &[u8]) -> StorageResult<()> { self.memory.write_file(path, data) }
545    fn read_file(&self, path: &str) -> StorageResult<Vec<u8>> { self.memory.read_file(path) }
546    fn delete_file(&self, path: &str) -> StorageResult<()> { self.memory.delete_file(path) }
547    fn file_exists(&self, path: &str) -> StorageResult<bool> { self.memory.file_exists(path) }
548    fn list_files(&self, dir: &str) -> StorageResult<Vec<String>> { self.memory.list_files(dir) }
549}
550
551/// Web storage backend stub.
552/// Production: localStorage (KV) + SubtleCrypto + encrypted localStorage (secure) + IndexedDB (files).
553pub struct WebStorageBackend {
554    memory: MemoryStorageBackend,
555}
556
557impl WebStorageBackend {
558    pub fn new() -> Self {
559        Self { memory: MemoryStorageBackend::new("web") }
560    }
561}
562
563impl StorageBackend for WebStorageBackend {
564    fn platform_id(&self) -> &str { "web" }
565    fn get(&self, ns: &StorageNamespace, key: &str) -> StorageResult<Option<StorageValue>> { self.memory.get(ns, key) }
566    fn set(&self, ns: &StorageNamespace, key: &str, value: StorageValue) -> StorageResult<()> { self.memory.set(ns, key, value) }
567    fn delete(&self, ns: &StorageNamespace, key: &str) -> StorageResult<()> { self.memory.delete(ns, key) }
568    fn keys(&self, ns: &StorageNamespace) -> StorageResult<Vec<String>> { self.memory.keys(ns) }
569    fn clear(&self, ns: &StorageNamespace) -> StorageResult<()> { self.memory.clear(ns) }
570    fn supports_secure(&self) -> bool { false } // Web has limited secure storage
571    fn write_file(&self, path: &str, data: &[u8]) -> StorageResult<()> { self.memory.write_file(path, data) }
572    fn read_file(&self, path: &str) -> StorageResult<Vec<u8>> { self.memory.read_file(path) }
573    fn delete_file(&self, path: &str) -> StorageResult<()> { self.memory.delete_file(path) }
574    fn file_exists(&self, path: &str) -> StorageResult<bool> { self.memory.file_exists(path) }
575    fn list_files(&self, dir: &str) -> StorageResult<Vec<String>> { self.memory.list_files(dir) }
576}
577
578/// Desktop storage backend stub (macOS, Windows, Linux).
579/// Production: file-based KV (JSON files) + OS keyring (secure) + file system (files).
580pub struct DesktopStorageBackend {
581    memory: MemoryStorageBackend,
582}
583
584impl DesktopStorageBackend {
585    pub fn new() -> Self {
586        Self { memory: MemoryStorageBackend::new("desktop") }
587    }
588}
589
590impl StorageBackend for DesktopStorageBackend {
591    fn platform_id(&self) -> &str { "desktop" }
592    fn get(&self, ns: &StorageNamespace, key: &str) -> StorageResult<Option<StorageValue>> { self.memory.get(ns, key) }
593    fn set(&self, ns: &StorageNamespace, key: &str, value: StorageValue) -> StorageResult<()> { self.memory.set(ns, key, value) }
594    fn delete(&self, ns: &StorageNamespace, key: &str) -> StorageResult<()> { self.memory.delete(ns, key) }
595    fn keys(&self, ns: &StorageNamespace) -> StorageResult<Vec<String>> { self.memory.keys(ns) }
596    fn clear(&self, ns: &StorageNamespace) -> StorageResult<()> { self.memory.clear(ns) }
597    fn supports_secure(&self) -> bool { true } // OS keyring
598    fn secure_set(&self, key: &str, value: &[u8]) -> StorageResult<()> { self.memory.secure_set(key, value) }
599    fn secure_get(&self, key: &str) -> StorageResult<Option<Vec<u8>>> { self.memory.secure_get(key) }
600    fn secure_delete(&self, key: &str) -> StorageResult<()> { self.memory.secure_delete(key) }
601    fn write_file(&self, path: &str, data: &[u8]) -> StorageResult<()> { self.memory.write_file(path, data) }
602    fn read_file(&self, path: &str) -> StorageResult<Vec<u8>> { self.memory.read_file(path) }
603    fn delete_file(&self, path: &str) -> StorageResult<()> { self.memory.delete_file(path) }
604    fn file_exists(&self, path: &str) -> StorageResult<bool> { self.memory.file_exists(path) }
605    fn list_files(&self, dir: &str) -> StorageResult<Vec<String>> { self.memory.list_files(dir) }
606}
607
608// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
609// Tests
610// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
611
612#[cfg(test)]
613mod tests {
614    use super::*;
615
616    fn make_manager() -> StorageManager {
617        StorageManager::new(Arc::new(MemoryStorageBackend::new("test")))
618    }
619
620    #[test]
621    fn kv_set_get_delete() {
622        let mgr = make_manager();
623        let ns = StorageNamespace::app_data();
624
625        // Initially empty
626        assert!(mgr.get_string(&ns, "name").unwrap().is_none());
627
628        // Set and get
629        mgr.set_string(&ns, "name", "AppScale").unwrap();
630        assert_eq!(mgr.get_string(&ns, "name").unwrap().unwrap(), "AppScale");
631
632        // Delete
633        mgr.backend.delete(&ns, "name").unwrap();
634        assert!(mgr.get_string(&ns, "name").unwrap().is_none());
635    }
636
637    #[test]
638    fn kv_namespaces_isolated() {
639        let mgr = make_manager();
640        let ns1 = StorageNamespace::app_data();
641        let ns2 = StorageNamespace::preferences();
642
643        mgr.set_string(&ns1, "key", "value1").unwrap();
644        mgr.set_string(&ns2, "key", "value2").unwrap();
645
646        assert_eq!(mgr.get_string(&ns1, "key").unwrap().unwrap(), "value1");
647        assert_eq!(mgr.get_string(&ns2, "key").unwrap().unwrap(), "value2");
648    }
649
650    #[test]
651    fn kv_clear_namespace() {
652        let mgr = make_manager();
653        let ns = StorageNamespace::cache();
654
655        mgr.set_string(&ns, "a", "1").unwrap();
656        mgr.set_string(&ns, "b", "2").unwrap();
657        mgr.backend.clear(&ns).unwrap();
658
659        assert!(mgr.get_string(&ns, "a").unwrap().is_none());
660        assert!(mgr.get_string(&ns, "b").unwrap().is_none());
661    }
662
663    #[test]
664    fn kv_list_keys() {
665        let mgr = make_manager();
666        let ns = StorageNamespace::app_data();
667
668        mgr.set_string(&ns, "alpha", "1").unwrap();
669        mgr.set_string(&ns, "beta", "2").unwrap();
670        mgr.set_string(&ns, "gamma", "3").unwrap();
671
672        let mut keys = mgr.backend.keys(&ns).unwrap();
673        keys.sort();
674        assert_eq!(keys, vec!["alpha", "beta", "gamma"]);
675    }
676
677    #[test]
678    fn kv_contains() {
679        let mgr = make_manager();
680        let ns = StorageNamespace::app_data();
681
682        assert!(!mgr.backend.contains(&ns, "missing").unwrap());
683        mgr.set_string(&ns, "present", "here").unwrap();
684        assert!(mgr.backend.contains(&ns, "present").unwrap());
685    }
686
687    #[test]
688    fn kv_json_roundtrip() {
689        let mgr = make_manager();
690        let ns = StorageNamespace::app_data();
691
692        #[derive(Debug, Serialize, Deserialize, PartialEq)]
693        struct Config { debug: bool, max_items: u32 }
694
695        let config = Config { debug: true, max_items: 50 };
696        mgr.set_json(&ns, "config", &config).unwrap();
697
698        let loaded: Config = mgr.get_json(&ns, "config").unwrap().unwrap();
699        assert_eq!(loaded, config);
700    }
701
702    #[test]
703    fn multi_get_set() {
704        let mgr = make_manager();
705        let ns = StorageNamespace::app_data();
706
707        mgr.multi_set(&ns, &[
708            ("x", StorageValue::Int(1)),
709            ("y", StorageValue::Int(2)),
710            ("z", StorageValue::Int(3)),
711        ]).unwrap();
712
713        let result = mgr.multi_get(&ns, &["x", "z", "missing"]).unwrap();
714        assert_eq!(result.len(), 2);
715        assert_eq!(result["x"], StorageValue::Int(1));
716        assert_eq!(result["z"], StorageValue::Int(3));
717    }
718
719    #[test]
720    fn secure_storage() {
721        let mgr = make_manager();
722        assert!(mgr.supports_secure());
723
724        mgr.secure_set_string("api_token", "secret123").unwrap();
725        let token = mgr.secure_get_string("api_token").unwrap().unwrap();
726        assert_eq!(token, "secret123");
727
728        mgr.backend.secure_delete("api_token").unwrap();
729        assert!(mgr.secure_get_string("api_token").unwrap().is_none());
730    }
731
732    #[test]
733    fn file_operations() {
734        let mgr = make_manager();
735
736        assert!(!mgr.backend.file_exists("docs/readme.txt").unwrap());
737
738        mgr.write_text("docs/readme.txt", "Hello, AppScale!").unwrap();
739        assert!(mgr.backend.file_exists("docs/readme.txt").unwrap());
740
741        let text = mgr.read_text("docs/readme.txt").unwrap();
742        assert_eq!(text, "Hello, AppScale!");
743
744        let files = mgr.backend.list_files("docs").unwrap();
745        assert_eq!(files, vec!["docs/readme.txt"]);
746
747        mgr.backend.delete_file("docs/readme.txt").unwrap();
748        assert!(!mgr.backend.file_exists("docs/readme.txt").unwrap());
749    }
750
751    #[test]
752    fn file_not_found() {
753        let mgr = make_manager();
754        let result = mgr.read_text("nonexistent.txt");
755        assert!(result.is_err());
756        assert!(matches!(
757            result.unwrap_err(),
758            StorageError::FileNotFound(_)
759        ));
760    }
761
762    #[test]
763    fn storage_value_conversions() {
764        let v: StorageValue = "hello".into();
765        assert_eq!(v.as_str(), Some("hello"));
766
767        let v: StorageValue = true.into();
768        assert_eq!(v.as_bool(), Some(true));
769
770        let v: StorageValue = 42i64.into();
771        assert_eq!(v.as_i64(), Some(42));
772
773        let v: StorageValue = 3.14f64.into();
774        assert_eq!(v.as_f64(), Some(3.14));
775
776        assert!(StorageValue::Null.is_null());
777    }
778
779    #[test]
780    fn platform_adapters_construct() {
781        let ios = IosStorageBackend::new();
782        assert_eq!(ios.platform_id(), "ios");
783        assert!(ios.supports_secure());
784
785        let android = AndroidStorageBackend::new();
786        assert_eq!(android.platform_id(), "android");
787        assert!(android.supports_secure());
788
789        let web = WebStorageBackend::new();
790        assert_eq!(web.platform_id(), "web");
791        assert!(!web.supports_secure());
792
793        let desktop = DesktopStorageBackend::new();
794        assert_eq!(desktop.platform_id(), "desktop");
795        assert!(desktop.supports_secure());
796    }
797
798    #[test]
799    fn namespace_prefixed_keys() {
800        let ns = StorageNamespace::new("myapp");
801        assert_eq!(ns.prefixed_key("user"), "myapp:user");
802        assert_eq!(ns.as_str(), "myapp");
803    }
804}