crab-vault 0.2.12

An object storage service written in rust.
Documentation
use std::{
    num::{ParseFloatError, ParseIntError},
    str::ParseBoolError,
};

use clap::{CommandFactory, error::ErrorKind};
use crab_vault::auth::error::AuthError;
use toml_edit::DatetimeParseError;

use crate::cli::Cli;

pub type CliResult<T> = Result<T, CliError>;

#[derive(Debug)]
pub struct CliError {
    kind: ErrorKind,
    general_message: String,
    source: Vec<String>,
}

#[derive(Default)]
pub struct MultiCliError {
    errors: Vec<CliError>,
}

impl MultiCliError {
    pub fn new() -> Self {
        Self { errors: vec![] }
    }

    pub fn add(&mut self, error: CliError) -> &mut Self {
        self.errors.push(error);
        self
    }

    pub fn exit_now(self) -> ! {
        let mut final_message = "".to_string();
        for e in self.errors {
            final_message.push_str(&format!("\n\n{}", e.into_message()));
        }

        Cli::command().error(ErrorKind::Io, final_message).exit()
    }

    pub fn is_empty(&self) -> bool {
        self.errors.len() == 0
    }
}

impl CliError {
    pub fn new(kind: ErrorKind, general_message: String, source: Option<String>) -> Self {
        Self {
            kind,
            general_message,
            source: match source {
                Some(val) => vec![val],
                None => vec![],
            },
        }
    }

    pub fn exit_now(self) -> ! {
        let (kind, message) = (self.kind, self.into_message());
        Cli::command()
            .error(kind, format!("\n\n    {message}"))
            .exit()
    }

    pub fn add_source(mut self, source: String) -> Self {
        self.source.push(source);
        self
    }

    pub fn into_message(self) -> String {
        if self.source.is_empty() {
            format!("    - {}", self.general_message)
        } else {
            let mut message = format!("    - {}", self.general_message);
            for src in self.source.into_iter().rev() {
                message.push_str(&format!("\n    | {src}"))
            }
            message
        }
    }
}

impl From<ParseIntError> for CliError {
    fn from(err: ParseIntError) -> Self {
        Self::new(
            ErrorKind::InvalidValue,
            format!("cannot transfer the value to an i64 value, details: {err}"),
            None,
        )
    }
}

impl From<ParseFloatError> for CliError {
    fn from(err: ParseFloatError) -> Self {
        Self::new(
            ErrorKind::InvalidValue,
            format!("cannot transfer the value to a f64 value, details: {err}"),
            None,
        )
    }
}

impl From<ParseBoolError> for CliError {
    fn from(err: ParseBoolError) -> Self {
        Self::new(
            ErrorKind::InvalidValue,
            format!("cannot transfer the value to a bool value, details: {err}"),
            None,
        )
    }
}

impl From<DatetimeParseError> for CliError {
    fn from(err: DatetimeParseError) -> Self {
        Self::new(
            ErrorKind::InvalidValue,
            format!("cannot transfer the value to a date time, details: {err}"),
            None,
        )
    }
}

impl From<std::io::Error> for CliError {
    fn from(err: std::io::Error) -> Self {
        Self::new(
            ErrorKind::Io,
            format!("io error occurred while reading configuration file, details: {err}"),
            None,
        )
    }
}

impl From<toml_edit::TomlError> for CliError {
    fn from(value: toml_edit::TomlError) -> Self {
        Self::new(
            ErrorKind::Io,
            format!("cannot parse the configuration file, details: {value}"),
            None,
        )
    }
}

impl From<base64::DecodeError> for CliError {
    fn from(value: base64::DecodeError) -> Self {
        Self::new(ErrorKind::Io, format!("base64 error: {}", value), None)
    }
}

impl From<AuthError> for CliError {
    fn from(value: AuthError) -> Self {
        let (general_message, source) = match value {
            AuthError::MissingAuthHeader => ("missing auth header".into(), None),
            AuthError::InvalidAuthFormat => ("invalid token format".into(), None),
            AuthError::InvalidKeyId => ("invalid key id".into(), None),
            AuthError::InvalidToken => ("token is invalid".into(), None),
            AuthError::TokenExpired => ("token expired".into(), None),
            AuthError::TokenNotYetValid => ("token not yet valid".into(), None),
            AuthError::InvalidSignature => ("token signature is invalid".into(), None),
            AuthError::InvalidAlgorithm(alg) => {
                (format!("cannot validate token encoded by {:?}", alg), None)
            }
            AuthError::InvalidIssuer => ("token is issued by untrusted issuer".into(), None),
            AuthError::InvalidAudience => ("token has invalid audience".into(), None),
            AuthError::InvalidSubject => ("subject of this token is invalid".into(), None),
            AuthError::MissingClaim(claim) => (format!("claim `{claim}` is absent"), None),
            AuthError::InsufficientPermissions => ("the permission is not sufficient".into(), None),
            AuthError::TokenRevoked => ("this token is revoked by the server".into(), None),
            AuthError::InvalidUtf8(e) => (
                format!("the token has some invalid utf-8 character, details: {e}"),
                None,
            ),
            AuthError::InvalidJson(e) => (
                format!("this token cannot be deserialized, details: {e}"),
                None,
            ),
            AuthError::InvalidBase64(e) => (
                format!("this token is not encoded in standard base64, details: {e}"),
                None,
            ),
            AuthError::InternalError(e) => (
                format!("something wrong while handling the token, details: {e}"),
                None,
            ),
        };

        Self::new(ErrorKind::Io, general_message, source)
    }
}

impl From<serde_json::Error> for CliError {
    fn from(value: serde_json::Error) -> Self {
        match value.classify() {
            serde_json::error::Category::Io => todo!(),
            serde_json::error::Category::Syntax => todo!(),
            serde_json::error::Category::Data => todo!(),
            serde_json::error::Category::Eof => todo!(),
        }
    }
}