beads_viewer_rust 0.2.1

Spec-first Rust port of beads_viewer (bv) — graph-aware triage for beads issue trackers (CLI binary: bvr)
Documentation
use std::path::PathBuf;

use thiserror::Error;

#[derive(Debug, Error)]
pub enum BvrError {
    #[error("io error: {0}")]
    Io(#[from] std::io::Error),

    #[error("json parse error: {0}")]
    Json(#[from] serde_json::Error),

    #[error("sqlite error: {0}")]
    Sqlite(#[from] rusqlite::Error),

    #[error("beads directory not found: {0}")]
    MissingBeadsDir(PathBuf),

    #[error("no beads JSONL file found in {0}")]
    MissingBeadsFile(PathBuf),

    #[error("invalid issue data: {0}")]
    InvalidIssue(String),

    #[error("invalid argument: {0}")]
    InvalidArgument(String),

    #[error("tui runtime error: {0}")]
    Tui(String),
}

pub type Result<T> = std::result::Result<T, BvrError>;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn io_error_display_includes_source() {
        let err = BvrError::Io(std::io::Error::new(std::io::ErrorKind::NotFound, "gone"));
        assert!(err.to_string().contains("io error"));
        assert!(err.to_string().contains("gone"));
    }

    #[test]
    fn json_error_converts_from_serde() {
        let bad_json = "{{invalid";
        let result: std::result::Result<serde_json::Value, _> = serde_json::from_str(bad_json);
        let serde_err = result.unwrap_err();
        let err: BvrError = serde_err.into();
        assert!(err.to_string().contains("json parse error"));
    }

    #[test]
    fn sqlite_error_converts_from_rusqlite() {
        let conn = rusqlite::Connection::open_in_memory().expect("open in-memory sqlite");
        let sqlite_err = conn
            .execute("SELECT definitely_not_valid_sql", [])
            .expect_err("invalid SQL should fail");
        let err: BvrError = sqlite_err.into();
        assert!(err.to_string().contains("sqlite error"));
    }

    #[test]
    fn missing_beads_dir_shows_path() {
        let err = BvrError::MissingBeadsDir(PathBuf::from("/tmp/nope"));
        assert!(err.to_string().contains("/tmp/nope"));
    }

    #[test]
    fn missing_beads_file_shows_path() {
        let err = BvrError::MissingBeadsFile(PathBuf::from("/tmp/.beads"));
        assert!(err.to_string().contains("/tmp/.beads"));
    }

    #[test]
    fn invalid_issue_shows_detail() {
        let err = BvrError::InvalidIssue("bad id".to_string());
        assert!(err.to_string().contains("bad id"));
    }

    #[test]
    fn invalid_argument_shows_detail() {
        let err = BvrError::InvalidArgument("--unknown".to_string());
        assert!(err.to_string().contains("--unknown"));
    }

    #[test]
    fn tui_error_shows_detail() {
        let err = BvrError::Tui("render crash".to_string());
        assert!(err.to_string().contains("render crash"));
    }

    #[test]
    fn error_debug_format_works() {
        let err = BvrError::InvalidArgument("test".to_string());
        let debug_str = format!("{err:?}");
        assert!(debug_str.contains("InvalidArgument"));
    }
}