soar-config 0.8.0

Configuration management for soar package manager
Documentation
use miette::Diagnostic;
use soar_utils::error::{PathError, UtilsError};
use thiserror::Error;

#[derive(Error, Diagnostic, Debug)]
pub enum ConfigError {
    #[error(transparent)]
    #[diagnostic(
        code(soar_config::toml_serialize),
        help("Check your configuration structure for invalid values")
    )]
    TomlSerError(#[from] toml::ser::Error),

    #[error(transparent)]
    #[diagnostic(
        code(soar_config::toml_deserialize),
        help("Check your config.toml syntax and structure")
    )]
    TomlDeError(#[from] toml::de::Error),

    #[error("Configuration file already exists")]
    #[diagnostic(
        code(soar_config::already_exists),
        help("Remove the existing config file or use a different location")
    )]
    ConfigAlreadyExists,

    #[error("Packages configuration file not found: {0}")]
    #[diagnostic(
        code(soar_config::packages_not_found),
        help("Create a packages.toml file or run `soar defpackages` to generate one")
    )]
    PackagesConfigNotFound(String),

    #[error("Packages configuration file already exists")]
    #[diagnostic(
        code(soar_config::packages_already_exists),
        help("Remove the existing packages.toml file or use a different location")
    )]
    PackagesConfigAlreadyExists,

    #[error("Invalid profile: {0}")]
    #[diagnostic(
        code(soar_config::invalid_profile),
        help("Check available profiles in your config file")
    )]
    InvalidProfile(String),

    #[error("Missing default profile: {0}")]
    #[diagnostic(
        code(soar_config::missing_default_profile),
        help("Ensure the default_profile field references an existing profile")
    )]
    MissingDefaultProfile(String),

    #[error("Missing profile: {0}")]
    #[diagnostic(
        code(soar_config::missing_profile),
        help("Add the profile to your configuration or use an existing one")
    )]
    MissingProfile(String),

    #[error("Invalid repository name: {0}")]
    #[diagnostic(code(soar_config::invalid_repository))]
    InvalidRepository(String),

    #[error("Invalid repository URL: {0}")]
    #[diagnostic(code(soar_config::invalid_repository_url))]
    InvalidRepositoryUrl(String),

    #[error("Reserved repository name 'local' cannot be used")]
    #[diagnostic(
        code(soar_config::reserved_repo_name),
        help("Choose a different name for your repository")
    )]
    ReservedRepositoryName,

    #[error("Duplicate repository name: {0}")]
    #[diagnostic(
        code(soar_config::duplicate_repo),
        help("Each repository must have a unique name")
    )]
    DuplicateRepositoryName(String),

    #[error(transparent)]
    #[diagnostic(code(soar_config::io))]
    IoError(#[from] std::io::Error),

    #[error(transparent)]
    #[diagnostic(code(soar_config::utils))]
    Utils(#[from] soar_utils::error::UtilsError),

    #[error(transparent)]
    #[diagnostic(code(soar_config::toml))]
    Toml(#[from] toml_edit::TomlError),

    #[error("Encountered unexpected TOML item: {0}")]
    #[diagnostic(code(soar_config::unexpected_toml_item))]
    UnexpectedTomlItem(String),

    #[error("Failed to annotate first table in array: {0}")]
    #[diagnostic(code(soar_config::annotate_first_table))]
    AnnotateFirstTable(String),

    #[error("{0}")]
    #[diagnostic(code(soar_config::custom))]
    Custom(String),
}

impl From<PathError> for ConfigError {
    fn from(err: PathError) -> Self {
        Self::Utils(UtilsError::Path(err))
    }
}

impl From<soar_utils::error::BytesError> for ConfigError {
    fn from(err: soar_utils::error::BytesError) -> Self {
        Self::Utils(UtilsError::Bytes(err))
    }
}

impl From<soar_utils::error::FileSystemError> for ConfigError {
    fn from(err: soar_utils::error::FileSystemError) -> Self {
        Self::Utils(UtilsError::FileSystem(err))
    }
}

pub type Result<T> = std::result::Result<T, ConfigError>;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_error_display() {
        let err = ConfigError::ConfigAlreadyExists;
        assert_eq!(err.to_string(), "Configuration file already exists");

        let err = ConfigError::InvalidProfile("test".to_string());
        assert_eq!(err.to_string(), "Invalid profile: test");

        let err = ConfigError::DuplicateRepositoryName("duplicate".to_string());
        assert_eq!(err.to_string(), "Duplicate repository name: duplicate");
    }

    #[test]
    fn test_error_from_conversions() {
        let path_err = soar_utils::error::PathError::MissingEnvVar {
            var: "SOAR_ROOT".to_string(),
            input: "$SOR_ROOT/test".to_string(),
        };
        let config_err: ConfigError = path_err.into();
        assert!(matches!(config_err, ConfigError::Utils(_)));
    }
}