aboutsummaryrefslogtreecommitdiff
path: root/src/list.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/list.rs')
-rw-r--r--src/list.rs230
1 files changed, 230 insertions, 0 deletions
diff --git a/src/list.rs b/src/list.rs
new file mode 100644
index 0000000..36cd492
--- /dev/null
+++ b/src/list.rs
@@ -0,0 +1,230 @@
+use anyhow::Result;
+use rusqlite::fallible_iterator::FallibleIterator;
+
+use crate::database::{DBExecutable, QueryResult};
+
+#[derive(PartialEq, Debug)]
+struct User {
+ name: Option<String>,
+ 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<QueryResult<Self::T>> {
+ 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<QueryResult<User>> {
+ 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<QueryResult<User>> {
+ 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<QueryResult<User>> {
+ 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<QueryResult<User>> {
+ 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))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use anyhow::Result;
+
+ use crate::{
+ database::{DB_NAME, Database},
+ list::User,
+ };
+
+ fn setup() -> Result<Database> {
+ std::fs::create_dir("test")?;
+ Database::new(&format!("test/{}", DB_NAME))
+ }
+
+ fn cleanup() -> Result<()> {
+ std::fs::remove_dir_all("test")?;
+ Ok(())
+ }
+
+ #[test]
+ fn 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 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 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 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");
+ }
+}
XMR address: 854DmXNrxULU3ZFJVs4Wc8PFhbq29RhqHhY8W6cdWrtFN3qmooKyyeYPcDzZTNRxphhJ5UzASQfAdEMwSteVqymk28aLhqj