smirrors 0.1.0

Automatic mirror list updater for Linux distributions
Documentation
use anyhow::Result;
use crate::config::Config;
use crate::utils::SMirrorsError;

/// Validate that all required directories exist and are accessible
pub fn validate_directories(config: &Config) -> Result<()> {
    let data_dir = Config::data_dir()?;
    let cache_dir = Config::cache_dir()?;

    // Check if directories exist or can be created
    if !data_dir.exists() {
        std::fs::create_dir_all(&data_dir)
            .map_err(|e| SMirrorsError::ConfigError(
                format!("Cannot create data directory {:?}: {}", data_dir, e)
            ))?;
    }

    if !cache_dir.exists() {
        std::fs::create_dir_all(&cache_dir)
            .map_err(|e| SMirrorsError::ConfigError(
                format!("Cannot create cache directory {:?}: {}", cache_dir, e)
            ))?;
    }

    // Check write permissions
    let test_file = data_dir.join(".write_test");
    std::fs::write(&test_file, "test")
        .map_err(|e| SMirrorsError::ConfigError(
            format!("Data directory {:?} is not writable: {}", data_dir, e)
        ))?;
    std::fs::remove_file(&test_file).ok();

    Ok(())
}

/// Validate static mirror URLs
pub fn validate_static_mirrors(config: &Config) -> Result<()> {
    for (name, url) in &config.static_mirrors {
        url::Url::parse(url)
            .map_err(|e| SMirrorsError::ConfigError(
                format!("Invalid static mirror URL for '{}': {} - {}", name, url, e)
            ))?;
    }

    Ok(())
}

/// Validate logging configuration
pub fn validate_logging(config: &Config) -> Result<()> {
    // Check log level is valid
    let valid_levels = ["trace", "debug", "info", "warn", "error"];
    if !valid_levels.contains(&config.logging.level.to_lowercase().as_str()) {
        return Err(SMirrorsError::ConfigError(
            format!("Invalid log level '{}'. Must be one of: trace, debug, info, warn, error",
                   config.logging.level)
        ).into());
    }

    // Check log format is valid
    let valid_formats = ["json", "pretty", "compact"];
    if !valid_formats.contains(&config.logging.format.to_lowercase().as_str()) {
        return Err(SMirrorsError::ConfigError(
            format!("Invalid log format '{}'. Must be one of: json, pretty, compact",
                   config.logging.format)
        ).into());
    }

    // If log file is specified, check parent directory exists
    if let Some(log_file) = &config.logging.file {
        if let Some(parent) = log_file.parent() {
            if !parent.exists() {
                std::fs::create_dir_all(parent)
                    .map_err(|e| SMirrorsError::ConfigError(
                        format!("Cannot create log directory {:?}: {}", parent, e)
                    ))?;
            }
        }
    }

    Ok(())
}

/// Perform complete configuration validation
pub fn validate_complete(config: &Config) -> Result<()> {
    // Run built-in validation
    config.validate()?;

    // Validate directories
    validate_directories(config)?;

    // Validate static mirrors
    validate_static_mirrors(config)?;

    // Validate logging
    validate_logging(config)?;

    Ok(())
}

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

    #[test]
    fn test_validate_default_config() {
        let config = Config::default();
        assert!(config.validate().is_ok());
    }

    #[test]
    fn test_validate_invalid_weights() {
        let mut config = Config::default();
        config.testing.speed_weight = 0.5;
        config.testing.latency_weight = 0.3;
        assert!(config.validate().is_err());
    }

    #[test]
    fn test_validate_negative_weights() {
        let mut config = Config::default();
        config.testing.speed_weight = -0.1;
        assert!(config.validate().is_err());
    }

    #[test]
    fn test_validate_invalid_min_score() {
        let mut config = Config::default();
        config.testing.min_score = 1.5;
        assert!(config.validate().is_err());
    }

    #[test]
    fn test_validate_invalid_concurrent_tests() {
        let mut config = Config::default();
        config.general.concurrent_tests = 0;
        assert!(config.validate().is_err());

        config.general.concurrent_tests = 101;
        assert!(config.validate().is_err());
    }
}