libbarto 0.6.1

A websocket based job scheduling system library for bartos, bartoc, barto-cli
Documentation
// Copyright (c) 2025 barto developers
//
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be copied,
// modified, or distributed except according to those terms.

use clap::error::ErrorKind;

/// Error types for the barto library
#[derive(Clone, Debug, thiserror::Error)]
pub enum Error {
    /// No valid config directory could be found
    #[error("There is no valid config directory")]
    ConfigDir,
    /// Unable to build a valid configuration
    #[error("Unable to build a valid configuration")]
    ConfigBuild,
    /// Unable to deserialize configuration
    #[error("Unable to deserialize config")]
    ConfigDeserialize,
    /// No valid data directory could be found
    #[error("There is no valid data directory")]
    DataDir,
    /// Unable to read the certificate file
    #[error("Unable to read the certificate file")]
    CertRead,
    /// Unable to read the private key file
    #[error("Unable to read the private key file")]
    KeyRead,
    /// No valid private keys found in the key file
    #[error("No valid private keys found in the key file")]
    NoPrivateKeys,
    /// No valid captures when parsing a realtime schedule
    #[error("no valid captures")]
    NoValidCaptures,
    /// An invalid range was specified when parsing a realtime schedule
    #[error("invalid range: '{}'", .0)]
    InvalidRange(String),
    /// An invalid first capture when parsing a realtime schedule
    #[error("invalid first capture")]
    InvalidFirstCapture,
    /// An invalid second capture when parsing a realtime schedule
    #[error("invalid second capture")]
    InvalidSecondCapture,
    /// An invalid time string was specified when parsing a realtime schedule
    #[error("invalid time string: '{}'", .0)]
    InvalidTime(String),
    /// An invalid date string was specified when parsing a realtime schedule
    #[error("invalid date string: '{}'", .0)]
    InvalidDate(String),
    /// An invalid calendar string was specified when parsing a realtime schedule
    #[error("invalid calendar string: '{}'", .0)]
    InvalidCalendar(String),
    /// An invalid query type was specified
    #[error("invalid query type")]
    InvalidQueryType,
    /// An invalid update kind was specified
    #[error("invalid update kind: '{}'", kind)]
    InvalidUpdateKind {
        /// The invalid update kind
        kind: String,
    },
    /// An invalid date string was specified when parsing a realtime schedule
    #[error("invalid day of week: '{}'", .0)]
    InvalidDayOfWeek(String),
    /// An invalid year string was specified when parsing a realtime schedule
    #[error("invalid year: '{}'", .0)]
    InvalidYear(String),
    /// An invalid month string was specified when parsing a realtime schedule
    #[error("invalid month: '{}'", .0)]
    InvalidMonth(String),
    /// An invalid day string was specified when parsing a realtime schedule
    #[error("invalid day: '{}'", .0)]
    InvalidDay(String),
    /// An invalid `MonthOfYear` string was specified when parsing a realtime schedule
    #[error("invalid month of year: '{}'", .0)]
    InvalidMonthOfYear(String),
    /// An invalid `DayOfMonth` string was specified when parsing a realtime schedule
    #[error("invalid day of month: '{}'", .0)]
    InvalidDayOfMonth(String),
    /// An invalid `HourOfDay` string was specified when parsing a realtime schedule
    #[error("invalid hour of day: '{}'", .0)]
    InvalidHourOfDay(String),
    /// An invalid `MinuteOfHour` string was specified when parsing a realtime schedule
    #[error("invalid minute of hour: '{}'", .0)]
    InvalidMinuteOfHour(String),
    /// An invalid `SecondOfMinute` string was specified when parsing a realtime schedule
    #[error("invalid second of minute: '{}'", .0)]
    InvalidSecondOfMinute(String),
}

/// Converts an `anyhow::Error` into a suitable exit code or clap message for a CLI application.
#[allow(clippy::needless_pass_by_value)]
#[must_use]
pub fn clap_or_error(err: anyhow::Error) -> i32 {
    let disp_err = || {
        eprintln!("{err:?}");
        1
    };
    match err.downcast_ref::<clap::Error>() {
        Some(e) => match e.kind() {
            ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => {
                println!("{e}");
                0
            }
            ErrorKind::InvalidValue
            | ErrorKind::UnknownArgument
            | ErrorKind::InvalidSubcommand
            | ErrorKind::NoEquals
            | ErrorKind::ValueValidation
            | ErrorKind::TooManyValues
            | ErrorKind::TooFewValues
            | ErrorKind::WrongNumberOfValues
            | ErrorKind::ArgumentConflict
            | ErrorKind::MissingRequiredArgument
            | ErrorKind::MissingSubcommand
            | ErrorKind::InvalidUtf8
            | ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
            | ErrorKind::Io
            | ErrorKind::Format => disp_err(),
            _ => unknown_err_kind(),
        },
        None => disp_err(),
    }
}

// Coverage ignore start: this is a catch-all for future ErrorKinds
#[cfg_attr(coverage_nightly, coverage(off))]
fn unknown_err_kind() -> i32 {
    eprintln!("Unknown ErrorKind");
    1
}

/// Indicates successful execution of a function, returning exit code 0.
#[must_use]
pub fn success((): ()) -> i32 {
    0
}

#[cfg(test)]
mod test {
    use super::{clap_or_error, success};

    use anyhow::{Error, anyhow};
    use clap::{
        Command,
        error::ErrorKind::{self, DisplayHelp, DisplayVersion},
    };

    #[test]
    fn test_success() {
        assert_eq!(success(()), 0);
    }

    #[test]
    fn clap_or_error_is_error() {
        assert_eq!(1, clap_or_error(anyhow!("test")));
    }

    #[test]
    fn clap_or_error_is_help() {
        let mut cmd = Command::new("bartos");
        let error = cmd.error(DisplayHelp, "help");
        let clap_error = Error::new(error);
        assert_eq!(0, clap_or_error(clap_error));
    }

    #[test]
    fn clap_or_error_is_version() {
        let mut cmd = Command::new("bartos");
        let error = cmd.error(DisplayVersion, "1.0");
        let clap_error = Error::new(error);
        assert_eq!(0, clap_or_error(clap_error));
    }

    #[test]
    fn clap_or_error_is_other_clap_error() {
        let mut cmd = Command::new("bartos");
        let error = cmd.error(ErrorKind::InvalidValue, "Some failure case");
        let clap_error = Error::new(error);
        assert_eq!(1, clap_or_error(clap_error));
    }
}