~gbmor/clinte

fd2f77093812a19536487bbf6928d02fb9b1f372 — Ben Morrison 1 year, 10 months ago 567a66d
letting errors flow up
4 files changed, 72 insertions(+), 71 deletions(-)

M src/error.rs
M src/logging.rs
M src/main.rs
M src/posts.rs
M src/error.rs => src/error.rs +6 -3
@@ 1,4 1,7 @@
pub fn helper<T, V>(res: Result<T, V>) -> T
// This Result is used elsewhere, not in helper()
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

pub fn helper<T, V>(res: std::result::Result<T, V>) -> T
where
    V: std::fmt::Debug,
{


@@ 17,7 20,7 @@ mod tests {

    #[test]
    fn shouldnt_panic() {
        let ok: Result<&str, &str> = Ok("okay");
        let ok: std::result::Result<&str, &str> = Ok("okay");
        let rhs = helper(ok);
        assert_eq!("okay", rhs);
    }


@@ 25,7 28,7 @@ mod tests {
    #[test]
    #[should_panic]
    fn should_panic() {
        let err: Result<&str, &str> = Err("oops");
        let err: std::result::Result<&str, &str> = Err("oops");
        helper(err);
    }
}

M src/logging.rs => src/logging.rs +7 -4
@@ 4,13 4,14 @@ use std::fs::File;
use chrono::offset::Utc;
use simplelog::*;

use crate::error;
use crate::user;

lazy_static! {
    static ref FILE: String = format!("/tmp/clinte_{}.log", *user::NAME);
}

pub fn init() {
pub fn init() -> error::Result<()> {
    // If the log file exists on startup,
    // move and timestamp it so we get a
    // fresh log file.


@@ 19,7 20,7 @@ pub fn init() {
        let time = Utc::now().to_rfc3339();
        new_file.push_str(".");
        new_file.push_str(&time);
        fs::rename(FILE.clone(), new_file).unwrap();
        fs::rename(FILE.clone(), new_file)?;
    }

    CombinedLogger::init(vec![


@@ 27,10 28,12 @@ pub fn init() {
        WriteLogger::new(
            LevelFilter::Info,
            Config::default(),
            File::create(FILE.clone()).unwrap(),
            File::create(FILE.clone())?,
        ),
    ])
    .expect("Unable to initialize logging");

    Ok(())
}

#[cfg(test)]


@@ 43,7 46,7 @@ mod tests {
    fn init_logs() {
        let blank = " ".bytes().collect::<Vec<u8>>();
        fs::write(&*FILE, &blank).unwrap();
        init();
        init().unwrap();

        info!("TEST LOG MESSAGE");
        let logfile = fs::read_to_string(&*FILE).unwrap();

M src/main.rs => src/main.rs +10 -8
@@ 13,7 13,7 @@ mod logging;
mod posts;
mod user;

fn main() {
fn main() -> error::Result<()> {
    let arg_matches = clap::App::new("clinte")
        .version(clap::crate_version!())
        .author("Ben Morrison (gbmor)")


@@ 24,7 24,7 @@ fn main() {
        .get_matches();

    let start = time::Instant::now();
    logging::init();
    logging::init()?;
    info!("clinte starting up!");
    println!("clinte v{}", clap::crate_version!());
    println!("a community notices system");


@@ 36,24 36,26 @@ fn main() {

    if arg_matches.subcommand_matches("post").is_some() {
        info!("New post...");
        posts::create(&db);
        posts::create(&db)?;
    } else if arg_matches.subcommand_matches("update").is_some() {
        let id: u32 = if let Some(val) = arg_matches.subcommand_matches("update_handler") {
            val.value_of("id").unwrap().parse().unwrap()
            val.value_of("id").unwrap().parse()?
        } else {
            0
        };
        info!("Updating post ...");
        posts::update_handler(&db, id);
        posts::update_handler(&db, id)?;
    } else if arg_matches.subcommand_matches("delete").is_some() {
        let id: u32 = if let Some(val) = arg_matches.subcommand_matches("update_handler") {
            val.value_of("id").unwrap().parse().unwrap()
            val.value_of("id").unwrap_or_else(|| "0").parse()?
        } else {
            0
        };
        info!("Deleting post");
        posts::delete_handler(&db, id);
        posts::delete_handler(&db, id)?;
    }

    posts::display(&db);
    posts::display(&db)?;

    Ok(())
}

M src/posts.rs => src/posts.rs +49 -56
@@ 1,17 1,15 @@
use std::error::Error;
use std::io;

use rusqlite;

use crate::db;
use crate::ed;
use crate::error;
use crate::user;

type Result<T> = std::result::Result<T, Box<dyn Error>>;

// Executes the sql statement that inserts a new post
// Broken off for unit testing.
pub fn exec_new(stmt: &mut rusqlite::Statement, title: &str, body: &str) -> Result<()> {
pub fn exec_new(stmt: &mut rusqlite::Statement, title: &str, body: &str) -> error::Result<()> {
    stmt.execute_named(&[
        (":title", &title),
        (":author", &*user::NAME),


@@ 33,16 31,15 @@ fn str_to_utf8(str: &str) -> String {
}

// First handler for creating a new post.
pub fn create(db: &db::Conn) {
pub fn create(db: &db::Conn) -> error::Result<()> {
    let mut stmt = db
        .conn
        .prepare("INSERT INTO posts (title, author, body) VALUES (:title, :author, :body)")
        .unwrap();
        .prepare("INSERT INTO posts (title, author, body) VALUES (:title, :author, :body)")?;

    println!();
    println!("Title of the new post: ");
    let mut title = String::new();
    io::stdin().read_line(&mut title).unwrap();
    io::stdin().read_line(&mut title)?;
    let title = str_to_utf8(title.trim());
    let title = if title.len() > 30 {
        &title[..30]


@@ 58,28 55,27 @@ pub fn create(db: &db::Conn) {
        &body
    };

    exec_new(&mut stmt, title, body).unwrap();
    exec_new(&mut stmt, title, body)?;

    println!();
    Ok(())
}

// Shows the most recent posts.
pub fn display(db: &db::Conn) {
    let mut stmt = db.conn.prepare("SELECT * FROM posts").unwrap();
    let out = stmt
        .query_map(rusqlite::NO_PARAMS, |row| {
            let id: u32 = row.get(0)?;
            let title: String = row.get(1)?;
            let author: String = row.get(2)?;
            let body: String = row.get(3)?;
            Ok(db::Post {
                id,
                title,
                author,
                body,
            })
pub fn display(db: &db::Conn) -> error::Result<()> {
    let mut stmt = db.conn.prepare("SELECT * FROM posts")?;
    let out = stmt.query_map(rusqlite::NO_PARAMS, |row| {
        let id: u32 = row.get(0)?;
        let title: String = row.get(1)?;
        let author: String = row.get(2)?;
        let body: String = row.get(3)?;
        Ok(db::Post {
            id,
            title,
            author,
            body,
        })
        .unwrap();
    })?;

    let mut postvec = Vec::new();
    out.for_each(|row| {


@@ 96,38 92,35 @@ pub fn display(db: &db::Conn) {
            print!("{}", e);
        }
    }

    Ok(())
}

// First handler to update posts.
pub fn update_handler(db: &db::Conn, id: u32) {
pub fn update_handler(db: &db::Conn, id: u32) -> error::Result<()> {
    let id_num_in = if id == 0 {
        println!();
        println!("ID number of your post to edit?");
        let mut id_num_in = String::new();
        io::stdin().read_line(&mut id_num_in).unwrap();
        id_num_in.trim().parse().unwrap()
        io::stdin().read_line(&mut id_num_in)?;
        id_num_in.trim().parse()?
    } else {
        id
    };

    let mut get_stmt = db
        .conn
        .prepare("SELECT * FROM posts WHERE id = :id")
        .unwrap();

    let row = get_stmt
        .query_row_named(&[(":id", &id_num_in)], |row| {
            let title: String = row.get(1).unwrap();
            let author = row.get(2).unwrap();
            let body = row.get(3).unwrap();
            Ok(vec![title, author, body])
        })
        .unwrap();
    let mut get_stmt = db.conn.prepare("SELECT * FROM posts WHERE id = :id")?;

    let row = get_stmt.query_row_named(&[(":id", &id_num_in)], |row| {
        let title: String = row.get(1)?;
        let author = row.get(2)?;
        let body = row.get(3)?;
        Ok(vec![title, author, body])
    })?;

    if *user::NAME != row[1] {
        println!();
        println!("Username mismatch - can't update_handler post!");
        return;
        return Ok(());
    }

    let mut new_title = String::new();


@@ 138,17 131,18 @@ pub fn update_handler(db: &db::Conn, id: u32) {
    println!("Title: {}\n\nBody: {}", row[0], row[2]);
    println!();
    println!("Enter new title:");
    io::stdin().read_line(&mut new_title).unwrap();
    io::stdin().read_line(&mut new_title)?;
    println!();
    println!("Enter new body:");
    io::stdin().read_line(&mut new_body).unwrap();
    io::stdin().read_line(&mut new_body)?;
    println!();

    update(&new_title, &new_body, id_num_in, &db).unwrap();
    update(&new_title, &new_body, id_num_in, &db)?;
    Ok(())
}

// Allows editing of posts - called by main::update
pub fn update(new_title: &str, new_body: &str, id_num_in: u32, db: &db::Conn) -> Result<()> {
pub fn update(new_title: &str, new_body: &str, id_num_in: u32, db: &db::Conn) -> error::Result<()> {
    let new_title = new_title.trim();
    let new_body = new_body.trim();



@@ 164,20 158,20 @@ pub fn update(new_title: &str, new_body: &str, id_num_in: u32, db: &db::Conn) ->
}

// Helper to just run a sql statement.
pub fn exec_stmt_no_params(stmt: &mut rusqlite::Statement) -> Result<()> {
pub fn exec_stmt_no_params(stmt: &mut rusqlite::Statement) -> error::Result<()> {
    stmt.execute(rusqlite::NO_PARAMS)?;

    Ok(())
}

// First handler to remove a post
pub fn delete_handler(db: &db::Conn, id: u32) {
pub fn delete_handler(db: &db::Conn, id: u32) -> error::Result<()> {
    let id_num_in: u32 = if id == 0 {
        println!();
        println!("ID of the post to delete?");
        let mut id_num_in = String::new();
        io::stdin().read_line(&mut id_num_in).unwrap();
        id_num_in.trim().parse().unwrap()
        io::stdin().read_line(&mut id_num_in)?;
        id_num_in.trim().parse()?
    } else {
        id
    };


@@ 185,21 179,20 @@ pub fn delete_handler(db: &db::Conn, id: u32) {
    let del_stmt = format!("DELETE FROM posts WHERE id = {}", id_num_in);
    let get_stmt = format!("SELECT * FROM posts WHERE id = {}", id_num_in);

    let mut get_stmt = db.conn.prepare(&get_stmt).unwrap();
    let mut del_stmt = db.conn.prepare(&del_stmt).unwrap();
    let mut get_stmt = db.conn.prepare(&get_stmt)?;
    let mut del_stmt = db.conn.prepare(&del_stmt)?;

    let user_in_post: String = get_stmt
        .query_row(rusqlite::NO_PARAMS, |row| row.get(2))
        .unwrap();
    let user_in_post: String = get_stmt.query_row(rusqlite::NO_PARAMS, |row| row.get(2))?;

    if *user::NAME != user_in_post {
        println!();
        println!("Users don't match. Can't delete!");
        println!();
        return;
        return Ok(());
    }

    exec_stmt_no_params(&mut del_stmt).unwrap();
    exec_stmt_no_params(&mut del_stmt)?;
    Ok(())
}

#[cfg(test)]