From da34b66eb442cd151ba3efdb3bae6c7015bf2e4e Mon Sep 17 00:00:00 2001 From: Cedric Hutchings Date: Wed, 12 Feb 2020 23:11:51 -0500 Subject: [PATCH] Use 'Result's instead of 'bool's in todo example. --- examples/todo/src/main.rs | 38 ++++++++++++++++++++----------------- examples/todo/src/task.rs | 39 ++++++++++++++++++++++---------------- examples/todo/src/tests.rs | 18 +++++++++--------- 3 files changed, 53 insertions(+), 42 deletions(-) diff --git a/examples/todo/src/main.rs b/examples/todo/src/main.rs index 8e17a21d..6b86c8f4 100644 --- a/examples/todo/src/main.rs +++ b/examples/todo/src/main.rs @@ -28,15 +28,21 @@ embed_migrations!(); pub struct DbConn(SqliteConnection); #[derive(Debug, Serialize)] -struct Context<'a, 'b>{ msg: Option<(&'a str, &'b str)>, tasks: Vec } +struct Context<'a>{ msg: Option<(&'a str, &'a str)>, tasks: Vec } -impl<'a, 'b> Context<'a, 'b> { - pub fn err(conn: &DbConn, msg: &'a str) -> Context<'static, 'a> { - Context{msg: Some(("error", msg)), tasks: Task::all(conn)} +impl<'a> Context<'a> { + pub fn err(conn: &DbConn, msg: &'a str) -> Context<'a> { + Context{msg: Some(("error", msg)), tasks: Task::all(conn).unwrap_or_default()} } - pub fn raw(conn: &DbConn, msg: Option<(&'a str, &'b str)>) -> Context<'a, 'b> { - Context{msg: msg, tasks: Task::all(conn)} + pub fn raw(conn: &DbConn, msg: Option<(&'a str, &'a str)>) -> Context<'a> { + match Task::all(conn) { + Ok(tasks) => Context{msg: msg, tasks}, + Err(_) => Context{ + msg: Some(("error", "Couldn't access the task database.")), + tasks: vec![] + } + } } } @@ -45,28 +51,26 @@ fn new(todo_form: Form, conn: DbConn) -> Flash { let todo = todo_form.into_inner(); if todo.description.is_empty() { Flash::error(Redirect::to("/"), "Description cannot be empty.") - } else if Task::insert(todo, &conn) { - Flash::success(Redirect::to("/"), "Todo successfully added.") + } else if let Err(e) = Task::insert(todo, &conn) { + Flash::error(Redirect::to("/"), &format!("Database error: {}", e)) } else { - Flash::error(Redirect::to("/"), "Whoops! The server failed.") + Flash::success(Redirect::to("/"), "Todo successfully added.") } } #[put("/")] fn toggle(id: i32, conn: DbConn) -> Result { - if Task::toggle_with_id(id, &conn) { - Ok(Redirect::to("/")) - } else { - Err(Template::render("index", &Context::err(&conn, "Couldn't toggle task."))) + match Task::toggle_with_id(id, &conn) { + Ok(()) => Ok(Redirect::to("/")), + Err(e) => Err(Template::render("index", &Context::err(&conn, &format!("Couldn't toggle task: {}", e)))), } } #[delete("/")] fn delete(id: i32, conn: DbConn) -> Result, Template> { - if Task::delete_with_id(id, &conn) { - Ok(Flash::success(Redirect::to("/"), "Todo was deleted.")) - } else { - Err(Template::render("index", &Context::err(&conn, "Couldn't delete task."))) + match Task::delete_with_id(id, &conn) { + Ok(()) => Ok(Flash::success(Redirect::to("/"), "Todo was deleted.")), + Err(e) => Err(Template::render("index", &Context::err(&conn, &format!("Couldn't delete task: {}", e)))), } } diff --git a/examples/todo/src/task.rs b/examples/todo/src/task.rs index 4d369172..6eda6a4c 100644 --- a/examples/todo/src/task.rs +++ b/examples/todo/src/task.rs @@ -1,4 +1,5 @@ -use diesel::{self, prelude::*}; +use diesel::{self, result::Error as DieselError, prelude::*}; +type Result = std::result::Result; mod schema { table! { @@ -27,32 +28,38 @@ pub struct Todo { } impl Task { - pub fn all(conn: &SqliteConnection) -> Vec { - all_tasks.order(tasks::id.desc()).load::(conn).unwrap() + pub fn all(conn: &SqliteConnection) -> Result> { + all_tasks.order(tasks::id.desc()).load::(conn) } - pub fn insert(todo: Todo, conn: &SqliteConnection) -> bool { + pub fn insert(todo: Todo, conn: &SqliteConnection) -> Result<()> { let t = Task { id: None, description: todo.description, completed: false }; - diesel::insert_into(tasks::table).values(&t).execute(conn).is_ok() + diesel::insert_into(tasks::table).values(&t).execute(conn)?; + + Ok(()) } - pub fn toggle_with_id(id: i32, conn: &SqliteConnection) -> bool { - let task = all_tasks.find(id).get_result::(conn); - if task.is_err() { - return false; - } + pub fn toggle_with_id(id: i32, conn: &SqliteConnection) -> Result<()> { + let task = all_tasks.find(id).get_result::(conn)?; - let new_status = !task.unwrap().completed; + let new_status = !task.completed; let updated_task = diesel::update(all_tasks.find(id)); - updated_task.set(task_completed.eq(new_status)).execute(conn).is_ok() + updated_task.set(task_completed.eq(new_status)).execute(conn)?; + + Ok(()) } - pub fn delete_with_id(id: i32, conn: &SqliteConnection) -> bool { - diesel::delete(all_tasks.find(id)).execute(conn).is_ok() + pub fn delete_with_id(id: i32, conn: &SqliteConnection) -> Result<()> { + // `.execute` returns a usize representing the number of + // database rows affected by this modification. + // In a larger app these metrics may be useful, + // but for a simple example we'll ignore them. + diesel::delete(all_tasks.find(id)).execute(conn).map(|_| ()) } #[cfg(test)] - pub fn delete_all(conn: &SqliteConnection) -> bool { - diesel::delete(all_tasks).execute(conn).is_ok() + pub fn delete_all(conn: &SqliteConnection) -> Result<()> { + // see comment in `.delete_with_id`. + diesel::delete(all_tasks).execute(conn).map(|_| ()) } } diff --git a/examples/todo/src/tests.rs b/examples/todo/src/tests.rs index e09a9a3a..fca043b5 100644 --- a/examples/todo/src/tests.rs +++ b/examples/todo/src/tests.rs @@ -18,7 +18,7 @@ macro_rules! run_test { let db = super::DbConn::get_one(&rocket); let $client = Client::new(rocket).expect("Rocket client"); let $conn = db.expect("failed to get database connection for testing"); - assert!(Task::delete_all(&$conn), "failed to delete all tasks for testing"); + Task::delete_all(&$conn).expect("failed to delete all tasks for testing"); $block }) @@ -28,7 +28,7 @@ macro_rules! run_test { fn test_insertion_deletion() { run_test!(|client, conn| { // Get the tasks before making changes. - let init_tasks = Task::all(&conn); + let init_tasks = Task::all(&conn).unwrap(); // Issue a request to insert a new task. client.post("/todo") @@ -37,7 +37,7 @@ fn test_insertion_deletion() { .dispatch(); // Ensure we have one more task in the database. - let new_tasks = Task::all(&conn); + let new_tasks = Task::all(&conn).unwrap(); assert_eq!(new_tasks.len(), init_tasks.len() + 1); // Ensure the task is what we expect. @@ -49,7 +49,7 @@ fn test_insertion_deletion() { client.delete(format!("/todo/{}", id)).dispatch(); // Ensure it's gone. - let final_tasks = Task::all(&conn); + let final_tasks = Task::all(&conn).unwrap(); assert_eq!(final_tasks.len(), init_tasks.len()); if final_tasks.len() > 0 { assert_ne!(final_tasks[0].description, "My first task"); @@ -66,16 +66,16 @@ fn test_toggle() { .body("description=test_for_completion") .dispatch(); - let task = Task::all(&conn)[0].clone(); + let task = Task::all(&conn).unwrap()[0].clone(); assert_eq!(task.completed, false); // Issue a request to toggle the task; ensure it is completed. client.put(format!("/todo/{}", task.id.unwrap())).dispatch(); - assert_eq!(Task::all(&conn)[0].completed, true); + assert_eq!(Task::all(&conn).unwrap()[0].completed, true); // Issue a request to toggle the task; ensure it's not completed again. client.put(format!("/todo/{}", task.id.unwrap())).dispatch(); - assert_eq!(Task::all(&conn)[0].completed, false); + assert_eq!(Task::all(&conn).unwrap()[0].completed, false); }) } @@ -86,7 +86,7 @@ fn test_many_insertions() { let rng = thread_rng(); run_test!(|client, conn| { // Get the number of tasks initially. - let init_num = Task::all(&conn).len(); + let init_num = Task::all(&conn).unwrap().len(); let mut descs = Vec::new(); for i in 0..ITER { @@ -101,7 +101,7 @@ fn test_many_insertions() { descs.insert(0, desc); // Ensure the task was inserted properly and all other tasks remain. - let tasks = Task::all(&conn); + let tasks = Task::all(&conn).unwrap(); assert_eq!(tasks.len(), init_num + i + 1); for j in 0..i {