use anyhow::Result; use rusqlite::fallible_iterator::FallibleIterator; use crate::database::{DBExecutable, QueryResult}; #[derive(PartialEq, Debug)] struct User { name: Option, email: String, } impl User { fn new(email: &str) -> Self { Self { name: None, email: String::from(email), } } fn insert(&self) -> UserQuery<'_> { let name = self.name.as_deref(); UserQuery::Insert(name, self.email.as_str()) } fn delete(&self) -> UserQuery<'_> { UserQuery::Delete(&self.email) } fn query_by_email(&self) -> UserQuery<'_> { UserQuery::QueryByEmail(&self.email) } fn query_all<'a>() -> UserQuery<'a> { UserQuery::QueryAll } } enum UserQuery<'a> { Insert(Option<&'a str>, &'a str), Delete(&'a str), QueryByEmail(&'a str), QueryAll, } impl DBExecutable for UserQuery<'_> { type T = User; fn execute(&self, tx: &rusqlite::Transaction) -> Result> { match self { UserQuery::Insert(_, _) => self.db_insert(tx), UserQuery::Delete(_) => self.db_delete(tx), UserQuery::QueryByEmail(_) => self.db_query_by_email(tx), UserQuery::QueryAll => self.db_query_all(tx), } } } impl UserQuery<'_> { fn db_insert(&self, tx: &rusqlite::Transaction<'_>) -> Result> { let (name, email) = if let UserQuery::Insert(name, email) = self { (name, email) } else { unreachable!("this should only be called by a UserQuery::Insert") }; if let Some(name) = name { let q = "INSERT INTO user (name, email) VALUES (?, ?)"; tx.execute(q, [name, email])?; return Ok(QueryResult::Empty); } let q = "INSERT INTO user (email) VALUES (?)"; tx.execute(q, [email])?; Ok(QueryResult::Empty) } fn db_delete(&self, tx: &rusqlite::Transaction<'_>) -> Result> { let email = if let UserQuery::Delete(email) = self { email } else { unreachable!("this should only be called by a UserQuery::Delete") }; let q = "DELETE FROM user WHERE email = ?"; tx.execute(q, [email])?; Ok(QueryResult::Empty) } fn db_query_by_email(&self, tx: &rusqlite::Transaction<'_>) -> Result> { let email = if let UserQuery::QueryByEmail(email) = self { email } else { unreachable!("this should only be called by a UserQuery::QueryByEmail") }; let q = "SELECT * FROM user WHERE email LIKE ?"; let mut stmt = tx.prepare(q)?; let ris = stmt .query([email])? .map(|row| { Ok(User { name: row.get(1).unwrap(), email: row.get(2).unwrap(), }) }) .collect()?; Ok(QueryResult::Vec(ris)) } fn db_query_all(&self, tx: &rusqlite::Transaction<'_>) -> Result> { let q = "SELECT * FROM user"; let mut stmt = tx.prepare(q)?; let ris = stmt .query(())? .map(|row| { Ok(User { name: row.get(1).unwrap(), email: row.get(2).unwrap(), }) }) .collect()?; Ok(QueryResult::Vec(ris)) } } #[derive(Debug, PartialEq)] struct List { address: String, desc: Option, } impl List { fn new(name: &str) -> Self { List { address: String::from(name), desc: None, } } fn insert(&self) -> ListQuery<'_> { ListQuery::Insert(&self.address, self.desc.as_deref()) } fn delete(&self) -> ListQuery<'_> { ListQuery::Delete(&self.address) } fn query_all<'a>() -> ListQuery<'a> { ListQuery::QueryAll } } enum ListQuery<'a> { Insert(&'a str, Option<&'a str>), Delete(&'a str), QueryByAddress(&'a str), QueryAll, } impl DBExecutable for ListQuery<'_> { type T = List; fn execute(&self, tx: &rusqlite::Transaction) -> Result> { match self { ListQuery::Insert(_, _) => self.db_insert(tx), ListQuery::Delete(_) => self.db_delete(tx), ListQuery::QueryByAddress(_) => self.db_query_by_address(tx), ListQuery::QueryAll => self.db_query_all(tx), } } } impl ListQuery<'_> { fn db_insert(&self, tx: &rusqlite::Transaction) -> Result> { let (name, desc) = if let ListQuery::Insert(name, desc) = self { (name, desc) } else { unreachable!("this should only be called by ListQuery::Insert"); }; if let Some(desc) = desc { let q = "INSERT INTO list (name, description) VALUES (?, ?)"; tx.execute(q, [name, desc])?; } else { let q = "INSERT INTO list (name) VALUES (?)"; tx.execute(q, [name])?; } Ok(QueryResult::Empty) } fn db_delete(&self, tx: &rusqlite::Transaction) -> Result> { let name = if let ListQuery::Delete(name) = self { name } else { unreachable!("this should only be called by ListQuery::Delete") }; let q = "DELETE FROM list WHERE name = ?"; tx.execute(q, [name])?; Ok(QueryResult::Empty) } fn db_query_by_address(&self, tx: &rusqlite::Transaction) -> Result> { let name = if let ListQuery::QueryByAddress(name) = self { name } else { unreachable!("this should only be called by ListQuery::QueryByName") }; let q = "SELECT * FROM list WHERE name = ?"; let mut stmt = tx.prepare(q)?; let ris = stmt .query([name])? .map(|row| { Ok(List { address: row.get(1).unwrap(), desc: row.get(2).unwrap(), }) }) .collect()?; Ok(QueryResult::Vec(ris)) } fn db_query_all(&self, tx: &rusqlite::Transaction) -> Result> { let q = "SELECT * FROM list"; let mut stmt = tx.prepare(q)?; let ris = stmt .query(())? .map(|row| { Ok(List { address: row.get(1).unwrap(), desc: row.get(2).unwrap(), }) }) .collect()?; Ok(QueryResult::Vec(ris)) } } #[cfg(test)] mod tests { use anyhow::Result; use crate::{ database::{DB_NAME, Database}, list::{List, User}, }; fn setup() -> Result { std::fs::create_dir("test")?; Database::new(&format!("test/{}", DB_NAME)) } fn cleanup() -> Result<()> { std::fs::remove_dir_all("test")?; Ok(()) } #[test] fn user_insert_wo_name() { let mut database = setup().expect("Failed setup"); let user = User { name: None, email: String::from("mroik@poul.org"), }; database.execute(user.insert()).expect("Failed insert"); let user2 = database .execute(user.query_by_email()) .expect("Failed query"); match user2 { crate::database::QueryResult::Empty => assert!(false), crate::database::QueryResult::Vec(items) => { assert_eq!(items.len(), 1); assert_eq!(items[0], user); } } drop(database); cleanup().expect("Failed cleanup"); } #[test] fn user_insert_with_name() { let mut database = setup().expect("Failed setup"); let user = User { name: Some(String::from("Mirko Faina")), email: String::from("mroik@poul.org"), }; database.execute(user.insert()).expect("Failed insert"); let user2 = database .execute(user.query_by_email()) .expect("Failed query"); match &user2 { crate::database::QueryResult::Empty => assert!(false), crate::database::QueryResult::Vec(items) => { assert_eq!(items.len(), 1); assert_eq!(items[0], user); } } drop(database); cleanup().expect("Failed cleanup"); } #[test] fn user_insert_twice() { let mut database = setup().expect("Failed setup"); let user = User { name: Some(String::from("Mirko Faina")), email: String::from("mroik@poul.org"), }; database.execute(user.insert()).expect("Failed insert"); let ris = database.execute(user.insert()); assert!(ris.is_err()); drop(database); cleanup().expect("Failed cleanup"); } #[test] fn user_delete() { let mut database = setup().expect("Failed setup"); let user = User { name: Some(String::from("Mirko Faina")), email: String::from("mroik@poul.org"), }; database.execute(user.insert()).expect("Failed insert"); database.execute(user.delete()).expect("Failed delete"); let ris = database .execute(User::query_all()) .expect("Failed query all"); match ris { crate::database::QueryResult::Empty => assert!(false), crate::database::QueryResult::Vec(items) => assert!(items.is_empty()), } drop(database); cleanup().expect("Failed cleanup"); } #[test] fn list_insert_wo_desc() { let mut database = setup().expect("failed setup"); let list = List { address: String::from("poul"), desc: None, }; database.execute(list.insert()).expect("failed insert"); let ris = database.execute(List::query_all()).expect("failed query"); match ris { crate::database::QueryResult::Empty => assert!(false), crate::database::QueryResult::Vec(items) => { assert_eq!(items.len(), 1); assert_eq!(items[0], list); } } cleanup().expect("failed cleanup"); } #[test] fn list_insert_with_desc() { let mut database = setup().expect("failed setup"); let list = List { address: String::from("poul"), desc: Some(String::from("The mailing list of the POuL")), }; database.execute(list.insert()).expect("failed insert"); let ris = database.execute(List::query_all()).expect("failed query"); match ris { crate::database::QueryResult::Empty => assert!(false), crate::database::QueryResult::Vec(items) => { assert_eq!(items.len(), 1); assert_eq!(items[0], list); } } drop(database); cleanup().expect("failed cleanup"); } #[test] fn list_insert_twice() { let mut database = setup().expect("failed setup"); let list = List { address: String::from("poul"), desc: Some(String::from("The mailing list of the POuL")), }; database.execute(list.insert()).expect("failed insert"); assert!(database.execute(list.insert()).is_err()); drop(database); cleanup().expect("failed cleanup"); } #[test] fn list_delete() { let mut database = setup().expect("failed setup"); let list = List { address: String::from("poul"), desc: Some(String::from("The mailing list of the POuL")), }; database.execute(list.insert()).expect("failed insert"); database.execute(list.delete()).expect("failed delete"); let ris = database .execute(List::query_all()) .expect("Failed query all"); match ris { crate::database::QueryResult::Empty => assert!(false), crate::database::QueryResult::Vec(items) => assert!(items.is_empty()), } drop(database); cleanup().expect("failed cleanup"); } }