cli-command 0.1.0

A lightweight and ergonomic command-line argument parser for Rust
Documentation
use std::error::Error;
use std::fmt;

/// The different kinds of errors that can occur during command line parsing.
#[derive(Clone, Debug)]
pub enum CliErrorKind {
    /// A required argument was not provided
    MissingArgument(String),
    /// A required parameter at a specific position was not provided
    MissingParameter(String, usize),
    /// General command line parsing error
    ParseCommandLine,
    /// An internal error occurred (e.g., during type conversion)
    Inner,
}

/// An error that occurred during command line parsing or argument processing.
///
/// This error type provides detailed information about what went wrong during
/// command line parsing, including helpful error messages for users.
#[derive(Debug)]
pub struct CliError {
    /// The underlying error that caused this error (if any)
    pub source: Option<Box<dyn Error>>,
    /// The specific kind of error that occurred
    pub kind: CliErrorKind,
}

impl CliError {
    /// Creates a new `CliError` with an underlying source error.
    ///
    /// This is typically used when an error occurs during type conversion
    /// or other internal operations.
    ///
    /// # Arguments
    /// * `source` - The underlying error that caused this error
    ///
    /// # Returns
    /// A new `CliError` with `kind` set to `Inner` and the provided source error.
    pub fn new_inner(source: Box<dyn Error>) -> Self {
        CliError {
            source: Some(source),
            kind: CliErrorKind::Inner,
        }
    }

    /// Creates a new `CliError` with a specific error kind.
    ///
    /// This is typically used for command line parsing errors that don't
    /// have an underlying source error.
    ///
    /// # Arguments
    /// * `kind` - The specific kind of error that occurred
    ///
    /// # Returns
    /// A new `CliError` with the provided kind and no source error.
    pub fn new_kind(kind: CliErrorKind) -> Self {
        CliError { source: None, kind }
    }
}

impl Error for CliError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        self.source.as_deref()
    }
}

impl CliErrorKind {
    pub fn into_boxed_error(self) -> Box<dyn Error> {
        let error: CliError = self.into();
        error.into()
    }
}

impl fmt::Display for CliErrorKind {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            CliErrorKind::MissingArgument(arg) =>
                write!(f, "Required argument '{}' not provided. Use --{} <value> to specify it.", arg, arg),
            CliErrorKind::MissingParameter(parameter, position) =>
                write!(f, "Missing parameter for '{}' at position {}. Expected: --{} <value1> <value2> ...", parameter, position, parameter),
            CliErrorKind::ParseCommandLine =>
                write!(f, "Failed to parse command line. Ensure arguments are properly formatted with - or -- prefixes."),
            CliErrorKind::Inner =>
                write!(f, "Internal error occurred during argument processing"),
        }
    }
}

impl fmt::Display for CliError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.kind)
    }
}

impl From<CliErrorKind> for CliError {
    fn from(kind: CliErrorKind) -> Self {
        Self::new_kind(kind)
    }
}

pub fn from_error<T>(error: T) -> CliError
where
    T: Error + Send + Sync + 'static,
{
    CliError::new_inner(Box::new(error))
}

macro_rules! impl_from_error {
    ($($error_type:ty),+ $(,)?) => {
        $(
            impl From<$error_type> for CliError {
                fn from(error: $error_type) -> Self {
                    from_error(error)
                }
            }
        )+
    };
}

impl_from_error!(
    std::io::Error,
    std::net::AddrParseError,
    std::num::ParseIntError,
    std::str::ParseBoolError,
    std::char::ParseCharError,
    std::string::ParseError,
    std::num::ParseFloatError,
);

impl From<Box<dyn Error>> for CliError {
    fn from(error: Box<dyn Error>) -> Self {
        Self::new_inner(error)
    }
}