komichi 2.2.0

Application tools for working with file-system paths
Documentation
//! Komichi errors.
//!
use super::util::scrub_path;
use camino::{FromPathBufError, Utf8Path};
use std::{ffi::OsStr, io, str::Utf8Error};
use thiserror::Error as ThisError;

#[derive(ThisError, Debug)]
#[error(transparent)]
pub struct IoErr(#[from] io::Error);

#[derive(ThisError, Debug)]
#[error(transparent)]
pub struct Utf8Err(#[from] Utf8Error);

#[derive(ThisError, Debug)]
pub enum FileError {
    #[error(transparent)]
    IoError(#[from] io::Error),
}

#[derive(ThisError, Debug)]
#[error(transparent)]
pub struct PathIoError(#[from] io::Error);

#[derive(ThisError, Debug)]
#[error("Malformed Path")]
pub struct PathConvertError {
    #[source]
    source: PathIoError,
}

impl PathConvertError {
    pub fn new(source: FromPathBufError) -> Self {
        let source = PathIoError(source.into_io_error());
        Self { source }
    }
}

#[derive(ThisError, Debug)]
pub enum HomeError {
    #[error("Unable to convert the retrieved user home directory: '{path}'")]
    RetrieveConvertError {
        path: String,
        source: PathConvertError,
    },
    #[error("Unable to retrieve the user home directory")]
    RetrieveLocateError,

    #[error("The home directory: '{0}' is not absolute")]
    NotAbsolute(String),
}

impl HomeError {
    pub fn retrieve_convert_error<T>(
        path: &T,
        source: PathConvertError,
    ) -> Self
    where
        T: AsRef<OsStr> + ?Sized,
    {
        let path = path.as_ref().to_string_lossy().to_string();
        Self::RetrieveConvertError { path, source }
    }
}

#[derive(ThisError, Debug)]
pub enum CwdError {
    #[error("Unable to convert the retrieved user's current working directory: '{path}'")]
    RetrieveConvertError {
        path: String,
        source: PathConvertError,
    },
    #[error(
        "Unable to retrieve the user's current working directory"
    )]
    RetrieveLocateError(#[source] io::Error),

    #[error("The current working directory: '{0}' is not absolute")]
    NotAbsolute(String),
}

impl CwdError {
    pub fn retrieve_convert_error<T>(
        path: &T,
        source: PathConvertError,
    ) -> Self
    where
        T: AsRef<OsStr> + ?Sized,
    {
        let path = scrub_path(&path, None);
        Self::RetrieveConvertError { path, source }
    }
}

#[derive(ThisError, Debug)]
pub enum ExpandPathLexerError {
    #[error("Missing the '$' symbol at position {0}")]
    MissingIdentifierSymbol(usize),

    #[error("Invalid starting identifer character '{wide_char} at position {position} must be _[a-z][A-Z]")]
    InvalidIdentifierStartCharacter {
        wide_char: String,
        position: usize,
    },

    #[error("Missing closing curly bracket '}}' for matching curly at position {0}")]
    MissingClosingCurly(usize),

    #[error("Missing the identifier name for the '$' symbol at position {0}")]
    MissingIdentifierName(usize),

    #[error("The tilde character '~' can only be followed by the path separator '{0}'")]
    InvalidTildeUse(String),
}

impl ExpandPathLexerError {
    pub fn invalid_identifier_start_character(
        wide_char: &str,
        position: usize,
    ) -> Self {
        let wide_char = wide_char.to_owned();
        Self::InvalidIdentifierStartCharacter {
            wide_char,
            position,
        }
    }
}

#[derive(ThisError, Debug)]
pub enum ExpandPathError {
    #[error("Unable to expand: {path}")]
    LexerError {
        path: String,
        #[source]
        source: ExpandPathLexerError,
    },
    #[error("Unable to expand: '{path}' the identifier '{identifier}' is not set or empty")]
    IdentifierExpandError { path: String, identifier: String },

    #[error("The home directory is unknown.")]
    UnknownHome,

    #[error("The current working directory is unknown.")]
    UnknownCwd,

    #[error("Cannot expand empty value")]
    EmptyPathError,

    #[error("Unable to set the home directory to: '{0}' because it's not an absolute path")]
    NotAbsoluteHome(String),

    #[error(
        "Unable to set the current working directory to: '{0}' because it's not an absolute path"
    )]
    NotAbsoluteCwd(String),
}

impl ExpandPathError {
    pub fn lexer_error<T>(
        path: &T,
        source: ExpandPathLexerError,
    ) -> Self
    where
        T: AsRef<OsStr> + ?Sized,
    {
        let path = scrub_path(&path, None);
        Self::LexerError { path, source }
    }
    pub fn identifier_expand_error<T>(
        path: &T,
        identifier: &str,
    ) -> Self
    where
        T: AsRef<OsStr> + ?Sized,
    {
        let path = scrub_path(&path, None);
        let identifier = identifier.to_string();
        Self::IdentifierExpandError { path, identifier }
    }
}

#[derive(ThisError, Debug)]
pub enum IdentifierKeyError {
    #[error("Non-ascii character")]
    NonAscii { position: usize },

    #[error("Must be alphanumeric or an underscore")]
    NonAlphaNumericUnderscore { position: usize },

    #[error("Missing identifier name")]
    EmptyIdentifierName { position: usize },

    #[error("Invalid identifier name")]
    InvalidName { position: usize },

    #[error(
        "Missing starting curly '{{'; use double '$$' to escape"
    )]
    MissingStartingCurly { position: usize },

    #[error("Missing closing curly '}}'")]
    MissingClosingCurly { position: usize },

    #[error("Identifier name cannot start with a number")]
    StartsWithNumber { position: usize },
}

impl IdentifierKeyError {
    pub fn non_ascii(position: usize) -> Self {
        let position = position + 1;
        Self::NonAscii { position }
    }

    pub fn non_alpha_numeric_underscore(position: usize) -> Self {
        let position = position + 1;
        Self::NonAlphaNumericUnderscore { position }
    }

    pub fn empty_identifier_name(position: usize) -> Self {
        let position = position + 1;
        Self::EmptyIdentifierName { position }
    }

    pub fn invalid_name(position: usize) -> Self {
        let position = position + 1;
        Self::InvalidName { position }
    }

    pub fn missing_starting_curly(position: usize) -> Self {
        let position = position + 1;
        Self::MissingStartingCurly { position }
    }
    pub fn missing_closing_curly(position: usize) -> Self {
        let position = position + 1;
        Self::MissingClosingCurly { position }
    }
    pub fn starts_with_number(position: usize) -> Self {
        let position = position + 1;
        Self::StartsWithNumber { position }
    }
}

#[derive(ThisError, Debug)]
pub enum ExpandIdentifierError {
    #[error("Unable to fetch the value for the identifier '{0}'")]
    ValueError(String),

    #[error("Identifier Error at line: {line_number} position: {position}")]
    KeyError {
        line_number: usize,
        position: usize,
        #[source]
        source: IdentifierKeyError,
    },
}

impl ExpandIdentifierError {
    pub fn key_error(
        line_number: usize,
        source: IdentifierKeyError,
    ) -> Self {
        let line_number = line_number + 1;
        let position = match source {
            IdentifierKeyError::NonAscii { position } => position,
            IdentifierKeyError::NonAlphaNumericUnderscore {
                position,
            } => position,
            IdentifierKeyError::EmptyIdentifierName { position } => {
                position
            },
            IdentifierKeyError::InvalidName { position } => position,
            IdentifierKeyError::MissingStartingCurly {
                position,
            } => position,
            IdentifierKeyError::MissingClosingCurly { position } => {
                position
            },
            IdentifierKeyError::StartsWithNumber { position } => {
                position
            },
        };
        Self::KeyError {
            line_number,
            position,
            source,
        }
    }

    pub fn value_error(name: &str) -> Self {
        let name = name.to_string();
        Self::ValueError(name)
    }
}

#[derive(ThisError, Debug)]
pub enum ExpandTextError {
    #[error("Error in file: '{path}'")]
    IdentifierErrorInFile {
        path: String,
        #[source]
        source: ExpandIdentifierError,
    },

    #[error("Unable to read file: '{path}'")]
    ReadFileError {
        path: String,
        #[source]
        source: IoErr,
    },

    #[error(transparent)]
    IdentifierError(#[from] ExpandIdentifierError),
}

impl ExpandTextError {
    pub fn identifier_error_in_file(
        path: &Utf8Path,
        source: ExpandIdentifierError,
    ) -> Self {
        let path = scrub_path(&path, None);
        Self::IdentifierErrorInFile { path, source }
    }

    pub fn read_file_error(
        path: &Utf8Path,
        source: std::io::Error,
    ) -> Self {
        let path = scrub_path(&path, None);
        let source = IoErr(source);
        Self::ReadFileError { path, source }
    }
}

#[derive(ThisError, Debug)]
pub enum KomichiError {
    #[error("Unable to convert: '{path}'")]
    PathConvertError {
        path: String,
        #[source]
        source: PathConvertError,
    },

    #[error(transparent)]
    HomeError(#[from] HomeError),

    #[error(transparent)]
    CwdError(#[from] CwdError),

    #[error(transparent)]
    ExpandPathError(#[from] ExpandPathError),

    #[error(transparent)]
    ExpandTextError(#[from] ExpandTextError),
}