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 <erik@zirkular.io>
This commit is contained in:
Matthias Beyer
2025-08-21 17:09:44 +02:00
committed by Erik Kundt
parent a2de36ddfb
commit 7a1ba8595a
2 changed files with 43 additions and 27 deletions
+29 -13
View File
@@ -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<OsString>),
@@ -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<Command> {
fn parse_args() -> anyhow::Result<Command, Error> {
use lexopt::prelude::*;
let mut parser = lexopt::Parser::from_env();
@@ -100,7 +118,7 @@ fn parse_args() -> anyhow::Result<Command> {
}
}
}
_ => 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<anyhow::Error>> {
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<anyhow::Error>> {
Ok(())
}
fn run_other(command: Option<&str>, args: &[OsString]) -> Result<(), Option<anyhow::Error>> {
fn run_other(command: Option<&str>, args: &[OsString]) -> Result<(), Error> {
match command {
Some("issue") => {
term::run_command_args::<tui_issue::Options, _>(
@@ -205,7 +221,7 @@ fn run_other(command: Option<&str>, args: &[OsString]) -> Result<(), Option<anyh
args.to_vec(),
);
}
command => term::run_rad(command, args),
command => term::run_rad(command, args).map_err(|err| err.into()),
}
}
+14 -14
View File
@@ -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<anyhow::Error>> {
#[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<anyhow::Error>> {
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 {