use crate::db::Database; /// Key-value settings store backed by the `settings` SQLite table. /// /// The table schema is: /// key TEXT PRIMARY KEY /// value TEXT NOT NULL /// /// All operations acquire the shared DB mutex for their duration and /// return immediately — no async needed for a local SQLite store. pub struct SettingsService { db: Database, } impl SettingsService { pub fn new(db: Database) -> Self { Self { db } } /// Retrieve the value for `key`, or `None` if it has never been set. pub fn get(&self, key: &str) -> Option { let conn = self.db.conn(); conn.query_row( "SELECT value FROM settings WHERE key = ?1", rusqlite::params![key], |row| row.get::<_, String>(0), ) .ok() } /// Insert or update the value for `key` (SQLite UPSERT). pub fn set(&self, key: &str, value: &str) -> Result<(), String> { let conn = self.db.conn(); conn.execute( "INSERT INTO settings (key, value) VALUES (?1, ?2) ON CONFLICT(key) DO UPDATE SET value = excluded.value", rusqlite::params![key, value], ) .map(|_| ()) .map_err(|e| format!("settings::set failed for key '{key}': {e}")) } /// Remove the entry for `key`. Succeeds silently if the key does not exist. pub fn delete(&self, key: &str) -> Result<(), String> { let conn = self.db.conn(); conn.execute( "DELETE FROM settings WHERE key = ?1", rusqlite::params![key], ) .map(|_| ()) .map_err(|e| format!("settings::delete failed for key '{key}': {e}")) } } // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- #[cfg(test)] mod tests { use super::*; use crate::db::Database; fn make_service() -> SettingsService { let db = Database::open(std::path::Path::new(":memory:")).unwrap(); db.migrate().unwrap(); SettingsService::new(db) } #[test] fn get_missing_returns_none() { let svc = make_service(); assert_eq!(svc.get("nonexistent"), None); } #[test] fn set_and_get_roundtrip() { let svc = make_service(); svc.set("theme", "dracula").unwrap(); assert_eq!(svc.get("theme"), Some("dracula".into())); } #[test] fn set_overwrites_existing_value() { let svc = make_service(); svc.set("theme", "dracula").unwrap(); svc.set("theme", "solarized").unwrap(); assert_eq!(svc.get("theme"), Some("solarized".into())); } #[test] fn delete_removes_key() { let svc = make_service(); svc.set("to_remove", "yes").unwrap(); svc.delete("to_remove").unwrap(); assert_eq!(svc.get("to_remove"), None); } #[test] fn delete_nonexistent_key_succeeds() { let svc = make_service(); // Should not return an error. assert!(svc.delete("ghost").is_ok()); } }