use crate::db::pool::{Connection, OptionalExtension, params}; use serde::{Deserialize, Serialize}; const ROLES: &[&str] = &["sent", "drafts", "trash", "junk", "archive"]; #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct SpecialFolders { pub sent: Option, pub drafts: Option, pub trash: Option, pub junk: Option, pub archive: Option, } fn valid_role(role: &str) -> bool { ROLES.contains(&role) } pub fn get_role(conn: &Connection, role: &str) -> Result, String> { if !valid_role(role) { return Err(format!("Invalid special folder role: {role}")); } conn.query_row(crate::dbq!(db_special_folders::Q_001), [role], |row| { row.get(0) }) .optional() .map_err(|e| format!("Failed to read special folder {role}: {e}")) } pub fn get_all(conn: &Connection) -> Result { Ok(SpecialFolders { sent: get_role(conn, "sent")?, drafts: get_role(conn, "drafts")?, trash: get_role(conn, "trash")?, junk: get_role(conn, "junk")?, archive: get_role(conn, "archive")?, }) } pub fn set_role(conn: &Connection, role: &str, folder_name: Option<&str>) -> Result<(), String> { if !valid_role(role) { return Err(format!("Invalid special folder role: {role}")); } match folder_name.map(str::trim).filter(|value| !value.is_empty()) { Some(folder) => { let exists: bool = conn .query_row(crate::dbq!(db_special_folders::Q_002), [folder], |row| { row.get(0) }) .map_err(|e| format!("Failed to validate folder {folder}: {e}"))?; if !exists { return Err(format!("Folder does not exist: {folder}")); } conn.execute( crate::dbq!(db_special_folders::Q_003), params![role, folder], ) .map_err(|e| format!("Failed to set special folder {role}: {e}"))?; } None => { conn.execute(crate::dbq!(db_special_folders::Q_004), [role]) .map_err(|e| format!("Failed to clear special folder {role}: {e}"))?; } } Ok(()) } pub fn update_all(conn: &Connection, folders: &SpecialFolders) -> Result { set_role(conn, "sent", folders.sent.as_deref())?; set_role(conn, "drafts", folders.drafts.as_deref())?; set_role(conn, "trash", folders.trash.as_deref())?; set_role(conn, "junk", folders.junk.as_deref())?; set_role(conn, "archive", folders.archive.as_deref())?; get_all(conn) } pub fn rename_folder_reference( conn: &Connection, old_name: &str, new_name: &str, ) -> Result<(), String> { conn.execute( crate::dbq!(db_special_folders::Q_005), params![new_name, old_name], ) .map_err(|e| format!("Failed to update special folder reference: {e}"))?; Ok(()) } pub fn clear_folder_reference(conn: &Connection, folder_name: &str) -> Result<(), String> { conn.execute(crate::dbq!(db_special_folders::Q_006), [folder_name]) .map_err(|e| format!("Failed to clear special folder reference: {e}"))?; Ok(()) } #[cfg(test)] mod tests { use super::*; use crate::db::folders; use crate::db::pool::open_test_db; #[test] fn special_folders_roundtrip() { let conn = open_test_db(); for name in ["Sent Items", "Drafts", "Deleted", "Junk", "Archive"] { folders::insert_folder_if_new(&conn, name, Some("/"), "").unwrap(); } let value = SpecialFolders { sent: Some("Sent Items".into()), drafts: Some("Drafts".into()), trash: Some("Deleted".into()), junk: Some("Junk".into()), archive: Some("Archive".into()), }; let saved = update_all(&conn, &value).unwrap(); assert_eq!(saved.sent.as_deref(), Some("Sent Items")); assert_eq!(saved.trash.as_deref(), Some("Deleted")); } #[test] fn rejects_unknown_folder() { let conn = open_test_db(); assert!(set_role(&conn, "sent", Some("Missing")).is_err()); } }