From 7a1ba8595ae408c381b5e8a2fb35efdc4ec20f45 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 17 Aug 2025 10:12:26 +0200 Subject: [PATCH] bin: Improve error handling This patch replaces `anyhow::Error` in the programs' entry functions with proper error types. This makes the semantics around errors that occur when forwarding commands to `rad` more clear. Signed-off-by: Erik Kundt --- bin/main.rs | 42 +++++++++++++++++++++++++++++------------- bin/terminal.rs | 28 ++++++++++++++-------------- 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/bin/main.rs b/bin/main.rs index 224a2fad..934b53b7 100644 --- a/bin/main.rs +++ b/bin/main.rs @@ -11,9 +11,10 @@ mod ui; use std::ffi::OsString; use std::io; -use std::io::Write; use std::{iter, process}; +use thiserror::Error; + use radicle::version::Version; use radicle_cli::terminal as cli_term; @@ -21,6 +22,8 @@ use radicle_cli::terminal as cli_term; use commands::*; use terminal as term; +use crate::terminal::ForwardError; + pub const NAME: &str = "rad-tui"; pub const DESCRIPTION: &str = "Radicle terminal interfaces"; pub const PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -33,6 +36,18 @@ pub const VERSION: Version = Version { timestamp: TIMESTAMP, }; +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}")] + Forward(#[from] term::ForwardError), + #[error("{0}")] + Args(#[from] lexopt::Error), + #[error("{0}")] + Json(#[from] serde_json::Error), + #[error("{0}")] + Other(#[from] anyhow::Error), +} + #[derive(Debug)] enum CommandName { Other(Vec), @@ -54,18 +69,21 @@ enum Command { } fn main() { - match parse_args().map_err(Some).and_then(run) { + match parse_args().and_then(run) { Ok(_) => process::exit(0), Err(err) => { - if let Some(err) = err { - radicle_term::error(format!("rad-tui: {err}")); + match err { + // Do not print an additonal error message if `rad` itself + // already printed its error(s). + Error::Forward(ForwardError::RadInternal) => {} + _ => radicle_term::error(format!("rad-tui: {err}")), } process::exit(1); } } } -fn parse_args() -> anyhow::Result { +fn parse_args() -> anyhow::Result { use lexopt::prelude::*; let mut parser = lexopt::Parser::from_env(); @@ -100,7 +118,7 @@ fn parse_args() -> anyhow::Result { } } } - _ => return Err(anyhow::anyhow!(arg.unexpected())), + _ => return Err(arg.unexpected().into()), } } @@ -150,15 +168,13 @@ fn print_help() -> anyhow::Result<()> { tui_help::run(Default::default(), cli_term::DefaultContext) } -fn run(command: Command) -> Result<(), Option> { +fn run(command: Command) -> Result<(), Error> { match command { Command::Version { json } => { let mut stdout = io::stdout(); if json { - VERSION - .write_json(&mut stdout) - .map_err(|e| Some(e.into()))?; - writeln!(&mut stdout).ok(); + VERSION.write_json(&mut stdout)?; + println!(); } else { println!("rad-tui {} ({})", VERSION.version, VERSION.commit); } @@ -182,7 +198,7 @@ fn run(command: Command) -> Result<(), Option> { Ok(()) } -fn run_other(command: Option<&str>, args: &[OsString]) -> Result<(), Option> { +fn run_other(command: Option<&str>, args: &[OsString]) -> Result<(), Error> { match command { Some("issue") => { term::run_command_args::( @@ -205,7 +221,7 @@ fn run_other(command: Option<&str>, args: &[OsString]) -> Result<(), Option term::run_rad(command, args), + command => term::run_rad(command, args).map_err(|err| err.into()), } } diff --git a/bin/terminal.rs b/bin/terminal.rs index 75ca3d86..d79435d8 100644 --- a/bin/terminal.rs +++ b/bin/terminal.rs @@ -1,36 +1,36 @@ use std::ffi::OsString; -use std::io::ErrorKind; use std::process; -use anyhow::anyhow; +use thiserror::Error; use radicle_cli::terminal; use radicle_cli::terminal::args; use radicle_cli::terminal::io; use radicle_cli::terminal::{Args, Command, DefaultContext, Error, Help}; -fn _run_rad(args: &[OsString]) -> Result<(), Option> { +#[derive(Error, Debug)] +pub enum ForwardError { + #[error("an internal error occured while executing 'rad'")] + RadInternal, + #[error("an I/O error occured while trying to forward command to 'rad': {0}")] + Io(#[from] std::io::Error), +} + +fn _run_rad(args: &[OsString]) -> Result<(), ForwardError> { let status = process::Command::new("rad").args(args).status(); match status { Ok(status) => { if !status.success() { - return Err(None); - } - } - Err(err) => { - if let ErrorKind::NotFound = err.kind() { - return Err(Some(anyhow!("'rad' was not found.",))); - } else { - return Err(Some(err.into())); + return Err(ForwardError::RadInternal); } + Ok(()) } + Err(err) => Err(err.into()), } - - Ok(()) } -pub fn run_rad(command: Option<&str>, args: &[OsString]) -> Result<(), Option> { +pub fn run_rad(command: Option<&str>, args: &[OsString]) -> Result<(), ForwardError> { let args = if let Some(command) = command { [vec![command.into()], args.to_vec()].concat() } else {