forked from mirrors/jj
cli: reorganize CommandError as (kind, err, hint) tuple
This helps to implement CommandError::add_hint(). The inner errors could be embedded in the enum as before, but they're mostly of the same type. And I think it's okay to use downcast_ref() to deal with the clap::Error special case.
This commit is contained in:
parent
7a2077a434
commit
aeb3470b5d
2 changed files with 85 additions and 52 deletions
|
@ -2587,11 +2587,14 @@ impl CliRunner {
|
|||
}
|
||||
|
||||
fn map_clap_cli_error(
|
||||
cmd_err: CommandError,
|
||||
mut cmd_err: CommandError,
|
||||
ui: &Ui,
|
||||
layered_configs: &LayeredConfigs,
|
||||
) -> CommandError {
|
||||
let CommandError::ClapCliError { err, hint: None } = &cmd_err else {
|
||||
if cmd_err.hint.is_some() {
|
||||
return cmd_err;
|
||||
}
|
||||
let Some(err) = cmd_err.error.downcast_ref::<clap::Error>() else {
|
||||
return cmd_err;
|
||||
};
|
||||
if let (Some(ContextValue::String(arg)), Some(ContextValue::String(value))) = (
|
||||
|
@ -2601,10 +2604,7 @@ fn map_clap_cli_error(
|
|||
if arg.as_str() == "--template <TEMPLATE>" && value.is_empty() {
|
||||
// Suppress the error, it's less important than the original error.
|
||||
if let Ok(template_aliases) = load_template_aliases(ui, layered_configs) {
|
||||
return CommandError::ClapCliError {
|
||||
err: err.clone(),
|
||||
hint: Some(format_template_aliases_hint(&template_aliases)),
|
||||
};
|
||||
cmd_err.hint = Some(format_template_aliases_hint(&template_aliases));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,22 +42,48 @@ use crate::revset_util::{self, UserRevsetEvaluationError};
|
|||
use crate::template_parser::{TemplateParseError, TemplateParseErrorKind};
|
||||
use crate::ui::Ui;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CommandError {
|
||||
UserError {
|
||||
err: Arc<dyn error::Error + Send + Sync>,
|
||||
hint: Option<String>,
|
||||
},
|
||||
ConfigError(Arc<dyn error::Error + Send + Sync>),
|
||||
/// Invalid command line
|
||||
CliError(Arc<dyn error::Error + Send + Sync>),
|
||||
/// Invalid command line detected by clap
|
||||
ClapCliError {
|
||||
err: Arc<clap::Error>,
|
||||
hint: Option<String>,
|
||||
},
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum CommandErrorKind {
|
||||
User,
|
||||
Config,
|
||||
/// Invalid command line. The inner error type may be `clap::Error`.
|
||||
Cli,
|
||||
BrokenPipe,
|
||||
InternalError(Arc<dyn error::Error + Send + Sync>),
|
||||
Internal,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CommandError {
|
||||
pub kind: CommandErrorKind,
|
||||
pub error: Arc<dyn error::Error + Send + Sync>,
|
||||
pub hint: Option<String>,
|
||||
}
|
||||
|
||||
impl CommandError {
|
||||
pub fn new(
|
||||
kind: CommandErrorKind,
|
||||
err: impl Into<Box<dyn error::Error + Send + Sync>>,
|
||||
) -> Self {
|
||||
CommandError {
|
||||
kind,
|
||||
error: Arc::from(err.into()),
|
||||
hint: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_hint_opt(
|
||||
kind: CommandErrorKind,
|
||||
err: impl Into<Box<dyn error::Error + Send + Sync>>,
|
||||
hint: Option<String>,
|
||||
) -> Self {
|
||||
CommandError {
|
||||
kind,
|
||||
error: Arc::from(err.into()),
|
||||
hint,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add method to attach hint?
|
||||
}
|
||||
|
||||
/// Wraps error with user-visible message.
|
||||
|
@ -110,14 +136,11 @@ pub fn user_error_with_hint_opt(
|
|||
err: impl Into<Box<dyn error::Error + Send + Sync>>,
|
||||
hint: Option<String>,
|
||||
) -> CommandError {
|
||||
CommandError::UserError {
|
||||
err: Arc::from(err.into()),
|
||||
hint,
|
||||
}
|
||||
CommandError::with_hint_opt(CommandErrorKind::User, err, hint)
|
||||
}
|
||||
|
||||
pub fn config_error(err: impl Into<Box<dyn error::Error + Send + Sync>>) -> CommandError {
|
||||
CommandError::ConfigError(Arc::from(err.into()))
|
||||
CommandError::new(CommandErrorKind::Config, err)
|
||||
}
|
||||
|
||||
pub fn config_error_with_message(
|
||||
|
@ -128,18 +151,18 @@ pub fn config_error_with_message(
|
|||
}
|
||||
|
||||
pub fn cli_error(err: impl Into<Box<dyn error::Error + Send + Sync>>) -> CommandError {
|
||||
CommandError::CliError(Arc::from(err.into()))
|
||||
CommandError::new(CommandErrorKind::Cli, err)
|
||||
}
|
||||
|
||||
pub fn internal_error(err: impl Into<Box<dyn error::Error + Send + Sync>>) -> CommandError {
|
||||
CommandError::InternalError(Arc::from(err.into()))
|
||||
CommandError::new(CommandErrorKind::Internal, err)
|
||||
}
|
||||
|
||||
pub fn internal_error_with_message(
|
||||
message: impl Into<String>,
|
||||
source: impl Into<Box<dyn error::Error + Send + Sync>>,
|
||||
) -> CommandError {
|
||||
CommandError::InternalError(Arc::new(ErrorWithMessage::new(message, source)))
|
||||
internal_error(ErrorWithMessage::new(message, source))
|
||||
}
|
||||
|
||||
fn format_similarity_hint<S: AsRef<str>>(candidates: &[S]) -> Option<String> {
|
||||
|
@ -157,11 +180,11 @@ fn format_similarity_hint<S: AsRef<str>>(candidates: &[S]) -> Option<String> {
|
|||
|
||||
impl From<io::Error> for CommandError {
|
||||
fn from(err: io::Error) -> Self {
|
||||
if err.kind() == io::ErrorKind::BrokenPipe {
|
||||
CommandError::BrokenPipe
|
||||
} else {
|
||||
user_error(err)
|
||||
}
|
||||
let kind = match err.kind() {
|
||||
io::ErrorKind::BrokenPipe => CommandErrorKind::BrokenPipe,
|
||||
_ => CommandErrorKind::User,
|
||||
};
|
||||
CommandError::new(kind, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -445,10 +468,7 @@ impl From<FsPathParseError> for CommandError {
|
|||
|
||||
impl From<clap::Error> for CommandError {
|
||||
fn from(err: clap::Error) -> Self {
|
||||
CommandError::ClapCliError {
|
||||
err: Arc::new(err),
|
||||
hint: None,
|
||||
}
|
||||
cli_error(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -480,40 +500,53 @@ fn try_handle_command_result(
|
|||
ui: &mut Ui,
|
||||
result: Result<(), CommandError>,
|
||||
) -> io::Result<ExitCode> {
|
||||
match &result {
|
||||
Ok(()) => Ok(ExitCode::SUCCESS),
|
||||
Err(CommandError::UserError { err, hint }) => {
|
||||
let Err(cmd_err) = &result else {
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
};
|
||||
let err = &cmd_err.error;
|
||||
match cmd_err.kind {
|
||||
CommandErrorKind::User => {
|
||||
writeln!(ui.error(), "Error: {err}")?;
|
||||
print_error_sources(ui, err.source())?;
|
||||
if let Some(hint) = hint {
|
||||
if let Some(hint) = &cmd_err.hint {
|
||||
writeln!(ui.hint(), "Hint: {hint}")?;
|
||||
}
|
||||
Ok(ExitCode::from(1))
|
||||
}
|
||||
Err(CommandError::ConfigError(err)) => {
|
||||
CommandErrorKind::Config => {
|
||||
writeln!(ui.error(), "Config error: {err}")?;
|
||||
print_error_sources(ui, err.source())?;
|
||||
if let Some(hint) = &cmd_err.hint {
|
||||
writeln!(ui.hint(), "Hint: {hint}")?;
|
||||
}
|
||||
writeln!(
|
||||
ui.hint(),
|
||||
"For help, see https://github.com/martinvonz/jj/blob/main/docs/config.md."
|
||||
)?;
|
||||
Ok(ExitCode::from(1))
|
||||
}
|
||||
Err(CommandError::CliError(err)) => {
|
||||
writeln!(ui.error(), "Error: {err}")?;
|
||||
print_error_sources(ui, err.source())?;
|
||||
Ok(ExitCode::from(2))
|
||||
CommandErrorKind::Cli => {
|
||||
if let Some(err) = err.downcast_ref::<clap::Error>() {
|
||||
handle_clap_error(ui, err, cmd_err.hint.as_deref())
|
||||
} else {
|
||||
writeln!(ui.error(), "Error: {err}")?;
|
||||
print_error_sources(ui, err.source())?;
|
||||
if let Some(hint) = &cmd_err.hint {
|
||||
writeln!(ui.hint(), "Hint: {hint}")?;
|
||||
}
|
||||
Ok(ExitCode::from(2))
|
||||
}
|
||||
}
|
||||
Err(CommandError::ClapCliError { err, hint }) => {
|
||||
handle_clap_error(ui, err, hint.as_deref())
|
||||
}
|
||||
Err(CommandError::BrokenPipe) => {
|
||||
CommandErrorKind::BrokenPipe => {
|
||||
// A broken pipe is not an error, but a signal to exit gracefully.
|
||||
Ok(ExitCode::from(BROKEN_PIPE_EXIT_CODE))
|
||||
}
|
||||
Err(CommandError::InternalError(err)) => {
|
||||
CommandErrorKind::Internal => {
|
||||
writeln!(ui.error(), "Internal error: {err}")?;
|
||||
print_error_sources(ui, err.source())?;
|
||||
if let Some(hint) = &cmd_err.hint {
|
||||
writeln!(ui.hint(), "Hint: {hint}")?;
|
||||
}
|
||||
Ok(ExitCode::from(255))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue