From f951e500f1fdf6ac212f01bfb2c978f8859de4a8 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Thu, 15 Jun 2023 16:31:29 +0700 Subject: [PATCH] Bubble up all db errors. Implement Deref and DerefMut for DBConn --- src/app.rs | 107 +++++++++++++++++++++++++++++---------------------- src/db.rs | 107 +++++++++++++++++++++++++++------------------------ src/input.rs | 39 ++++++++++--------- 3 files changed, 138 insertions(+), 115 deletions(-) diff --git a/src/app.rs b/src/app.rs index 650f8e7..ebee09b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -63,14 +63,13 @@ pub struct State<'a> { impl<'a> State<'a> { /// Creates a new [`State`]. /// - /// # Panics + /// # Errors /// - /// Panics if we can't get all the columns from the database - #[must_use] + /// Returns an error if we can't read the database columns pub fn new(conn: Connection) -> Result { let db_conn = db::DBConn::new(conn); let columns = db_conn.get_all_columns()?; - let selected_column = db_conn.get_selected_column(); + let selected_column = db_conn.get_selected_column()?; Ok(State { columns, selected_column_idx: selected_column, @@ -89,14 +88,14 @@ impl<'a> State<'a> { &mut self.columns[self.selected_column_idx] } - pub fn select_previous_column(&mut self) { + pub fn select_previous_column(&mut self) -> Result<(), Error> { self.selected_column_idx = self.selected_column_idx.saturating_sub(1); - self.db_conn.set_selected_column(self.selected_column_idx); + self.db_conn.set_selected_column(self.selected_column_idx) } - pub fn select_next_column(&mut self) { + pub fn select_next_column(&mut self) -> Result<(), Error> { self.selected_column_idx = min(self.selected_column_idx + 1, self.columns.len() - 1); - self.db_conn.set_selected_column(self.selected_column_idx); + self.db_conn.set_selected_column(self.selected_column_idx) } #[must_use] @@ -126,16 +125,18 @@ impl<'a> State<'a> { column.tasks.get_mut(column.selected_task_idx) } - pub fn select_previous_task(&mut self) { + pub fn select_previous_task(&mut self) -> Result<(), Error> { let column = self.get_selected_column_mut(); column.selected_task_idx = column.selected_task_idx.saturating_sub(1); let task_idx = column.selected_task_idx; let col_id = column.id; - self.db_conn.set_selected_task_for_column(task_idx, col_id); + self.db_conn + .set_selected_task_for_column(task_idx, col_id)?; + Ok(()) } - pub fn select_next_task(&mut self) { + pub fn select_next_task(&mut self) -> Result<(), Error> { let column = self.get_selected_column_mut(); column.selected_task_idx = min( column.selected_task_idx + 1, @@ -144,25 +145,31 @@ impl<'a> State<'a> { let task_idx = column.selected_task_idx; let col_id = column.id; - self.db_conn.set_selected_task_for_column(task_idx, col_id); + self.db_conn + .set_selected_task_for_column(task_idx, col_id)?; + Ok(()) } - pub fn select_first_task(&mut self) { + pub fn select_first_task(&mut self) -> Result<(), Error> { let column = self.get_selected_column_mut(); column.selected_task_idx = 0; let task_idx = column.selected_task_idx; let col_id = column.id; - self.db_conn.set_selected_task_for_column(task_idx, col_id); + self.db_conn + .set_selected_task_for_column(task_idx, col_id)?; + Ok(()) } - pub fn select_last_task(&mut self) { + pub fn select_last_task(&mut self) -> Result<(), Error> { let column = self.get_selected_column_mut(); column.selected_task_idx = column.tasks.len().saturating_sub(1); let task_idx = column.selected_task_idx; let col_id = column.id; - self.db_conn.set_selected_task_for_column(task_idx, col_id); + self.db_conn + .set_selected_task_for_column(task_idx, col_id)?; + Ok(()) } #[must_use] @@ -175,16 +182,16 @@ impl<'a> State<'a> { }) } - pub fn move_task_up(&mut self) { - self.move_task(false); + pub fn move_task_up(&mut self) -> Result<(), Error> { + self.move_task(false) } - pub fn move_task_down(&mut self) { - self.move_task(true); + pub fn move_task_down(&mut self) -> Result<(), Error> { + self.move_task(true) } /// Returns the move task down of this [`State`]. - pub fn move_task(&mut self, is_down: bool) { + pub fn move_task(&mut self, is_down: bool) -> Result<(), Error> { let other_task = if is_down { self.get_next_task() } else { @@ -205,74 +212,80 @@ impl<'a> State<'a> { } let col_id = column.id; - self.db_conn.swap_task_order(t2_id, t1_id); - self.db_conn.set_selected_task_for_column(task_idx, col_id); + self.db_conn.swap_task_order(t2_id, t1_id)?; + self.db_conn + .set_selected_task_for_column(task_idx, col_id)?; } + Ok(()) } - pub fn move_task_previous_column(&mut self) { - self.move_task_to_column(false); + pub fn move_task_previous_column(&mut self) -> Result<(), Error> { + self.move_task_to_column(false) } - pub fn move_task_next_column(&mut self) { - self.move_task_to_column(true); + pub fn move_task_next_column(&mut self) -> Result<(), Error> { + self.move_task_to_column(true) } - fn move_task_to_column(&mut self, move_right: bool) { + fn move_task_to_column(&mut self, move_right: bool) -> Result<(), Error> { let can_move_right = move_right && self.selected_column_idx < self.columns.len() - 1; let can_move_left = !move_right && self.selected_column_idx > 0; let first_col = self.get_selected_column_mut(); if first_col.tasks.is_empty() || !can_move_right && !can_move_left { - return; + // We're at the bounds so just ignore + return Ok(()); } let t = first_col.tasks.remove(first_col.selected_task_idx); // Only move it if it was the last task if first_col.selected_task_idx == first_col.tasks.len() { - self.select_previous_task(); + self.select_previous_task()?; } if move_right { - self.select_next_column(); + self.select_next_column()?; } else { - self.select_previous_column(); + self.select_previous_column()?; } let col = self.get_selected_column_mut(); col.tasks.push(t); - self.select_last_task(); + self.select_last_task()?; if let Some(task) = self.get_selected_task() { let col = self.get_selected_column(); - self.db_conn.move_task_to_column(task, col); - self.db_conn.set_selected_column(self.selected_column_idx); + self.db_conn.move_task_to_column(task, col)?; + self.db_conn.set_selected_column(self.selected_column_idx)?; } + Ok(()) } - pub fn add_new_task(&mut self, title: String, description: String) { + pub fn add_new_task(&mut self, title: String, description: String) -> Result<(), Error> { let col_id = self.get_selected_column().id; - let task = self.db_conn.insert_new_task(title, description, col_id); + let task = self.db_conn.create_new_task(title, description, col_id)?; - self.select_last_task(); + self.select_last_task()?; let selected_task_idx = self.get_selected_column().selected_task_idx; self.db_conn - .set_selected_task_for_column(selected_task_idx, col_id); + .set_selected_task_for_column(selected_task_idx, col_id)?; self.get_selected_column_mut().tasks.push(task); - self.select_last_task() + self.select_last_task()?; + Ok(()) } - pub fn edit_task(&mut self, title: String, description: String) { + pub fn edit_task(&mut self, title: String, description: String) -> Result<(), Error> { if let Some(selected_task) = self.get_selected_task_mut() { selected_task.title = title; selected_task.description = description; let cloned = selected_task.clone(); - self.db_conn.update_task_text(&cloned); + self.db_conn.update_task_text(&cloned)?; } + Ok(()) } /// Delete the currently selected task from the selected column - pub fn delete_task(&mut self) { + pub fn delete_task(&mut self) -> Result<(), Error> { if let Some(task) = self.get_selected_task() { let task_id = task.id; let column = self.get_selected_column_mut(); @@ -282,12 +295,14 @@ impl<'a> State<'a> { column.tasks.remove(task_idx); if column.selected_task_idx >= column.tasks.len() { - self.select_previous_task(); + self.select_previous_task()?; task_idx = task_idx.saturating_sub(1); } - self.db_conn.delete_task(task_id); - self.db_conn.set_selected_task_for_column(task_idx, col_id); + self.db_conn.delete_task(task_id)?; + self.db_conn + .set_selected_task_for_column(task_idx, col_id)?; } + Ok(()) } } diff --git a/src/db.rs b/src/db.rs index 2c4f870..71fb9eb 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,8 +1,24 @@ +use anyhow::Error; +use std::ops::{Deref, DerefMut}; use crate::{Column, Task}; use rusqlite::{params, Connection, Result}; pub struct DBConn(Connection); +impl DerefMut for DBConn { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Deref for DBConn { + type Target = Connection; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl DBConn { pub fn new(conn: Connection) -> Self { DBConn(conn) @@ -14,7 +30,7 @@ impl DBConn { /// /// This function will return an error if something is wrong with the SQL pub fn get_tasks_by_column(&self, column_name: &String) -> Result> { - let mut stmt = self.0.prepare( + let mut stmt = self.prepare( r#" select task.id, title, description from task join kb_column on column_id = kb_column.id @@ -42,9 +58,7 @@ impl DBConn { /// /// This function will return an error if there are issues with the SQL pub fn get_all_columns(&self) -> Result> { - let mut stmt = self - .0 - .prepare("select id, name, selected_task from kb_column")?; + let mut stmt = self.prepare("select id, name, selected_task from kb_column")?; let columns = stmt .query_map((), |row| { let name = row.get(1)?; @@ -65,19 +79,21 @@ impl DBConn { /// # Panics /// /// Panics if something goes wrong with the SQL - pub fn insert_new_task(&self, title: String, description: String, column_id: i64) -> Task { - let mut stmt = self - .0 - .prepare("insert into task(title, description, column_id) values (?1, ?2, ?3)") - .unwrap(); - stmt.execute(params![title, description, column_id]) - .unwrap(); - let id = self.0.last_insert_rowid(); - Task { + pub fn create_new_task( + &self, + title: String, + description: String, + column_id: i64, + ) -> Result { + let mut stmt = + self.prepare("insert into task(title, description, column_id) values (?1, ?2, ?3)")?; + stmt.execute(params![title, description, column_id])?; + let id = self.last_insert_rowid(); + Ok(Task { id, title, description, - } + }) } /// . @@ -85,9 +101,10 @@ impl DBConn { /// # Panics /// /// Panics if something goes wrong with the SQL - pub fn delete_task(&self, task_id: i64) { - let mut stmt = self.0.prepare("delete from task where id = ?1").unwrap(); - stmt.execute([task_id]).unwrap(); + pub fn delete_task(&self, task_id: i64) -> Result<()> { + let mut stmt = self.prepare("delete from task where id = ?1")?; + stmt.execute([task_id])?; + Ok(()) } /// . @@ -95,13 +112,11 @@ impl DBConn { /// # Panics /// /// Panics if something goes wrong with the SQL - pub fn update_task_text(&self, task: &Task) { + pub fn update_task_text(&self, task: &Task) -> Result<()> { let mut stmt = self - .0 - .prepare("update task set title = ?2, description = ?3 where id = ?1") - .unwrap(); - stmt.execute((&task.id, &task.title, &task.description)) - .unwrap(); + .prepare("update task set title = ?2, description = ?3 where id = ?1")?; + stmt.execute((&task.id, &task.title, &task.description))?; + Ok(()) } /// . @@ -109,9 +124,8 @@ impl DBConn { /// # Panics /// /// Panics if something goes wrong with the SQL - pub fn move_task_to_column(&self, task: &Task, target_column: &Column) { + pub fn move_task_to_column(&self, task: &Task, target_column: &Column) -> Result<()> { let mut stmt = self - .0 .prepare( "update task set @@ -124,7 +138,8 @@ impl DBConn { ) .unwrap(); stmt.execute((&task.id, &target_column.id)).unwrap(); - self.set_selected_task_for_column(target_column.selected_task_idx, target_column.id); + self.set_selected_task_for_column(target_column.selected_task_idx, target_column.id)?; + Ok(()) } /// . @@ -132,8 +147,8 @@ impl DBConn { /// # Panics /// /// Panics if something goes wrong with the SQL - pub fn swap_task_order(&mut self, task1_id: i64, task2_id: i64) { - let tx = self.0.transaction().unwrap(); + pub fn swap_task_order(&mut self, task1_id: i64, task2_id: i64) -> Result<()> { + let tx = self.transaction().unwrap(); tx.execute( "create temp table temp_order as select sort_order from task where id = ?1", @@ -156,54 +171,46 @@ impl DBConn { tx.execute("drop table temp_order", ()).unwrap(); tx.commit().unwrap(); + Ok(()) } /// # Panics /// /// Panics if something goes wrong with the SQL - pub fn set_selected_column(&self, column_id: usize) { + pub fn set_selected_column(&self, column_id: usize) -> Result<(), Error> { let mut stmt = self - .0 - .prepare("insert or replace into app_state(key, value) values (?1, ?2)") - .unwrap(); - stmt.execute((&"selected_column", column_id.to_string())) - .unwrap(); + .prepare("insert or replace into app_state(key, value) values (?1, ?2)")?; + stmt.execute((&"selected_column", column_id.to_string()))?; + Ok(()) } /// # Panics /// /// Panics if something goes wrong with the SQL - pub fn get_selected_column(&self) -> usize { + pub fn get_selected_column(&self) -> Result { let mut stmt = self - .0 - .prepare("select value from app_state where key = ?1") - .unwrap(); + .prepare("select value from app_state where key = ?1")?; stmt.query_row(["selected_column"], |row| { let value: String = row.get::(0).unwrap(); Ok(value.parse::().unwrap()) }) - .unwrap() } /// # Panics /// /// Panics if something goes wrong with the SQL - pub fn set_selected_task_for_column(&self, task_idx: usize, column_id: i64) { - let mut stmt = self - .0 - .prepare("update kb_column set selected_task = ?2 where id = ?1") - .unwrap(); - stmt.execute((column_id, task_idx)).unwrap(); + pub fn set_selected_task_for_column(&self, task_idx: usize, column_id: i64) -> Result<()> { + let mut stmt = self.prepare("update kb_column set selected_task = ?2 where id = ?1")?; + stmt.execute((column_id, task_idx))?; + Ok(()) } /// # Panics /// /// Panics if something goes wrong with the SQL - pub fn get_selected_task_for_column(&self, column_id: i32) -> usize { + pub fn get_selected_task_for_column(&self, column_id: i32) -> Result { let mut stmt = self - .0 - .prepare("select selected_task from kb_column where key = ?1") - .unwrap(); - stmt.query_row([column_id], |row| row.get(0)).unwrap() + .prepare("select selected_task from kb_column where key = ?1")?; + stmt.query_row([column_id], |row| row.get(0)) } } diff --git a/src/input.rs b/src/input.rs index bf66dba..dabba5a 100644 --- a/src/input.rs +++ b/src/input.rs @@ -2,8 +2,7 @@ use crate::app::{State, TaskEditFocus, TaskState}; use crossterm::event; use crossterm::event::{Event, KeyCode}; -// pub fn handle_task_edit(state: &mut State<'_>, key: event::KeyEvent, mut task: TaskState<'_>) { -pub fn handle_task_edit(state: &mut State<'_>, key: event::KeyEvent) { +pub fn handle_task_edit(state: &mut State<'_>, key: event::KeyEvent) -> Result<(), anyhow::Error> { if let Some(mut task) = state.task_edit_state.take() { let mut clear_task = false; match task.focus { @@ -30,9 +29,9 @@ pub fn handle_task_edit(state: &mut State<'_>, key: event::KeyEvent) { let title = task.title.clone().into_lines().join("\n"); let description = task.description.clone().into_lines().join("\n"); if task.is_edit { - state.edit_task(title, description); + state.edit_task(title, description)?; } else { - state.add_new_task(title, description); + state.add_new_task(title, description)?; } clear_task = true; } @@ -51,59 +50,61 @@ pub fn handle_task_edit(state: &mut State<'_>, key: event::KeyEvent) { state.task_edit_state = Some(task); } } + Ok(()) } -pub fn handle_main(state: &mut State<'_>, key: event::KeyEvent) { +pub fn handle_main(state: &mut State<'_>, key: event::KeyEvent) -> Result<(), anyhow::Error> { match key.code { KeyCode::Char('q') => state.quit = true, KeyCode::Char('h') | KeyCode::Left => { - state.select_previous_column(); + state.select_previous_column()?; } KeyCode::Char('j') | KeyCode::Down => { - state.select_next_task(); + state.select_next_task()?; } KeyCode::Char('k') | KeyCode::Up => { - state.select_previous_task(); + state.select_previous_task()?; } KeyCode::Char('l') | KeyCode::Right => { - state.select_next_column(); + state.select_next_column()?; } KeyCode::Char('g') => { - state.select_first_task(); + state.select_first_task()?; } KeyCode::Char('G') => { - state.select_last_task(); + state.select_last_task()?; } KeyCode::Char('H') => { - state.move_task_previous_column(); + state.move_task_previous_column()?; } KeyCode::Char('L') => { - state.move_task_next_column(); + state.move_task_next_column()?; } KeyCode::Char('J') => { - state.move_task_down(); + state.move_task_down()?; } KeyCode::Char('K') => { - state.move_task_up(); + state.move_task_up()?; } KeyCode::Char('n') => state.task_edit_state = Some(TaskState::default()), KeyCode::Char('e') => state.task_edit_state = state.get_task_state_from_current(), KeyCode::Char('D') => { - state.delete_task(); + state.delete_task()?; } _ => {} } + Ok(()) } /// # Errors /// /// Crossterm `event::read()` might return an error -pub fn handle(state: &mut State<'_>) -> Result<(), std::io::Error> { +pub fn handle(state: &mut State<'_>) -> Result<(), anyhow::Error> { if let Event::Key(key) = event::read()? { if state.task_edit_state.is_some() { - handle_task_edit(state, key); + handle_task_edit(state, key)?; } else { - handle_main(state, key); + handle_main(state, key)?; } } Ok(())