naru-config 0.7.0

A security-first configuration manager with encryption and audit logging
Documentation
use unicode_normalization::UnicodeNormalization;

fn normalize_unicode(input: &str) -> String {
    input.nfc().collect()
}

pub fn validate_environment_name(name: &str) -> Result<(), &'static str> {
    let normalized = normalize_unicode(name);

    if normalized.is_empty() {
        return Err("Environment name cannot be empty");
    }

    if name.contains('\0') {
        return Err("Environment name contains null bytes");
    }

    if !normalized
        .chars()
        .all(|c| c.is_alphanumeric() || c == '_' || c == '-')
    {
        return Err(
            "Environment name contains invalid characters. Only alphanumeric, underscore, and hyphen are allowed.",
        );
    }

    if normalized.len() > 100 {
        return Err("Environment name is too long (max 100 characters)");
    }

    Ok(())
}

pub fn validate_config_key(key: &str) -> Result<(), &'static str> {
    if key.contains('\0') {
        return Err("Configuration key contains null bytes");
    }

    let normalized = normalize_unicode(key);

    if normalized.is_empty() {
        return Err("Configuration key cannot be empty");
    }

    if !normalized
        .chars()
        .all(|c| c.is_alphanumeric() || c == '_' || c == '-' || c == '.')
    {
        return Err(
            "Configuration key contains invalid characters. Only alphanumeric, underscore, hyphen, and dot are allowed.",
        );
    }

    if normalized.len() > 255 {
        return Err("Configuration key is too long (max 255 characters)");
    }

    Ok(())
}

pub fn normalize_environment_name(name: &str) -> String {
    normalize_unicode(name)
}

pub fn normalize_config_key(key: &str) -> String {
    normalize_unicode(key)
}

pub fn sanitize_string_value(value: &str) -> String {
    value.replace("\0", "")
}

pub fn is_valid_environment_name(name: &str) -> bool {
    validate_environment_name(name).is_ok()
}

pub fn is_valid_config_key(key: &str) -> bool {
    validate_config_key(key).is_ok()
}

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

    #[test]
    fn test_validate_environment_name_valid() {
        assert!(validate_environment_name("dev").is_ok());
        assert!(validate_environment_name("production_env").is_ok());
        assert!(validate_environment_name("staging-test").is_ok());
    }

    #[test]
    fn test_validate_environment_name_invalid() {
        assert!(validate_environment_name("").is_err());
        assert!(validate_environment_name("dev;rm -rf /").is_err());
        assert!(validate_environment_name("dev/../../../etc/passwd").is_err());
    }

    #[test]
    fn test_validate_environment_name_length() {
        assert!(validate_environment_name(&"a".repeat(100)).is_ok());
        assert!(validate_environment_name(&"a".repeat(101)).is_err());
    }

    #[test]
    fn test_validate_config_key_valid() {
        assert!(validate_config_key("DB_HOST").is_ok());
        assert!(validate_config_key("api.key").is_ok());
        assert!(validate_config_key("my-key_123").is_ok());
    }

    #[test]
    fn test_validate_config_key_invalid() {
        assert!(validate_config_key("").is_err());
        assert!(validate_config_key("key with spaces").is_err());
        assert!(validate_config_key("key!@#").is_err());
    }

    #[test]
    fn test_validate_config_key_length() {
        assert!(validate_config_key(&"a".repeat(255)).is_ok());
        assert!(validate_config_key(&"a".repeat(256)).is_err());
    }

    #[test]
    fn test_normalize_environment_name() {
        let normalized = normalize_environment_name("test-env");
        assert_eq!(normalized, "test-env");
    }

    #[test]
    fn test_normalize_config_key() {
        let normalized = normalize_config_key("my.key");
        assert_eq!(normalized, "my.key");
    }

    #[test]
    fn test_sanitize_string_value() {
        assert_eq!(sanitize_string_value("hello"), "hello");
        assert_eq!(sanitize_string_value("hel\0lo"), "hello");
    }

    #[test]
    fn test_is_valid_environment_name() {
        assert!(is_valid_environment_name("dev"));
        assert!(!is_valid_environment_name("dev env"));
    }

    #[test]
    fn test_is_valid_config_key() {
        assert!(is_valid_config_key("API_KEY"));
        assert!(!is_valid_config_key("key with space"));
    }

    #[test]
    fn test_validate_environment_injection() {
        assert!(validate_environment_name("dev;rm -rf /").is_err());
        assert!(validate_environment_name("production\n").is_err());
        assert!(validate_environment_name("staging\0").is_err());
    }
}