use clap::error::ErrorKind as ClapErrorKind;
use colored::Colorize;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CliError {
#[error("{0}")]
Message(String),
#[error(transparent)]
Anyhow(#[from] anyhow::Error),
}
pub type CliResult<T> = Result<T, CliError>;
#[derive(Debug, Clone, Copy)]
pub enum ErrorKindTag {
Parse,
Validation,
Dependency,
Permission,
Operation,
Config,
Runtime,
}
impl ErrorKindTag {
fn label(self) -> &'static str {
match self {
ErrorKindTag::Parse => "PARSE",
ErrorKindTag::Validation => "VALIDATION",
ErrorKindTag::Dependency => "DEPENDENCY",
ErrorKindTag::Permission => "PERMISSION",
ErrorKindTag::Operation => "OPERATION",
ErrorKindTag::Config => "CONFIG",
ErrorKindTag::Runtime => "RUNTIME",
}
}
}
pub struct ErrorFactory;
impl ErrorFactory {
pub fn parse(message: impl Into<String>, hint: Option<&str>) -> CliError {
Self::build(ErrorKindTag::Parse, "cli", message, hint, None)
}
pub fn validation(component: &str, message: impl Into<String>, hint: Option<&str>) -> CliError {
Self::build(ErrorKindTag::Validation, component, message, hint, None)
}
pub fn dependency(component: &str, message: impl Into<String>, hint: Option<&str>) -> CliError {
Self::build(ErrorKindTag::Dependency, component, message, hint, None)
}
pub fn permission(component: &str, message: impl Into<String>, hint: Option<&str>) -> CliError {
Self::build(ErrorKindTag::Permission, component, message, hint, None)
}
pub fn config(component: &str, message: impl Into<String>, hint: Option<&str>) -> CliError {
Self::build(ErrorKindTag::Config, component, message, hint, None)
}
pub fn operation(
component: &str,
action: &str,
source: impl Into<String>,
hint: Option<&str>,
) -> CliError {
let message = format!("{} failed", action);
Self::build(
ErrorKindTag::Operation,
component,
message,
hint,
Some(source.into()),
)
}
pub fn runtime(component: &str, message: impl Into<String>, hint: Option<&str>) -> CliError {
Self::build(ErrorKindTag::Runtime, component, message, hint, None)
}
pub fn clap_parse(err: clap::Error) -> CliError {
let hint = Self::clap_hint(err.kind());
Self::parse(err.to_string(), hint)
}
fn clap_hint(kind: ClapErrorKind) -> Option<&'static str> {
match kind {
ClapErrorKind::InvalidSubcommand => {
Some("Run `xbp --help` to list available commands.")
}
ClapErrorKind::UnknownArgument | ClapErrorKind::InvalidValue => {
Some("Use `-h` with your subcommand to see supported options.")
}
ClapErrorKind::MissingRequiredArgument => {
Some("Check the usage block above and provide required arguments.")
}
_ => None,
}
}
fn build(
kind: ErrorKindTag,
component: &str,
message: impl Into<String>,
hint: Option<&str>,
details: Option<String>,
) -> CliError {
let header = format!("[{}] {}", kind.label(), component)
.bright_red()
.bold();
let mut lines = vec![header.to_string(), format!(" {}", message.into())];
if let Some(details) = details {
lines.push(format!(" {} {}", "Details:".bright_black(), details));
}
if let Some(hint) = hint {
lines.push(format!(" {} {}", "Hint:".bright_yellow().bold(), hint));
}
CliError::Message(lines.join("\n"))
}
}
impl From<String> for CliError {
fn from(value: String) -> Self {
CliError::Message(value)
}
}
impl From<&str> for CliError {
fn from(value: &str) -> Self {
CliError::Message(value.to_string())
}
}