threatflux-core 0.2.0

Core types and utilities for ThreatFlux security platform
Documentation
use regex::Regex;
use std::sync::LazyLock;

pub static USERNAME_REGEX: LazyLock<Regex> =
    LazyLock::new(|| Regex::new(r"^[a-zA-Z0-9_-]+$").unwrap());

/// Validate if a username follows the correct format
#[must_use]
pub fn is_valid_username(username: &str) -> bool {
    if username.len() < 3 || username.len() > 32 {
        return false;
    }
    USERNAME_REGEX.is_match(username)
}

/// Validate if an email has a basic valid format
#[must_use]
pub fn is_valid_email(email: &str) -> bool {
    if email.len() <= 3 {
        return false;
    }

    let at_pos = email.find('@');
    at_pos.is_some_and(|pos| pos > 0 && pos < email.len() - 1)
}

/// Validate if a password meets minimum requirements
#[must_use]
pub const fn is_valid_password(password: &str) -> bool {
    password.len() >= 8
}

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

    #[test]
    fn test_is_valid_username() {
        let long_username = "a".repeat(33);
        let test_cases = vec![
            ("valid_user", true),
            ("user123", true),
            ("user-name", true),
            ("user_name", true),
            ("User", true),
            ("ab", false),                   // too short
            (long_username.as_str(), false), // too long
            ("user@name", false),            // invalid character @
            ("user name", false),            // invalid character space
            ("user.name", false),            // invalid character .
            ("user#name", false),            // invalid character #
            ("", false),                     // empty
        ];

        for (username, expected) in test_cases {
            assert_eq!(
                is_valid_username(username),
                expected,
                "Failed for username: {username}"
            );
        }
    }

    #[rstest]
    #[case("test@example.com", true)]
    #[case("user@domain.org", true)]
    #[case("a@b.c", true)]
    #[case("test", false)] // no @
    #[case("@domain.com", false)] // no local part
    #[case("test@", false)] // no domain
    #[case("", false)] // empty
    #[case("ab", false)] // too short
    fn test_is_valid_email(#[case] email: &str, #[case] expected: bool) {
        assert_eq!(is_valid_email(email), expected);
    }

    #[test]
    fn test_is_valid_password() {
        let min_password = "a".repeat(8);
        let test_cases = vec![
            ("password123", true),
            ("12345678", true),
            (min_password.as_str(), true),
            ("1234567", false), // too short
            ("", false),        // empty
        ];

        for (password, expected) in test_cases {
            assert_eq!(
                is_valid_password(password),
                expected,
                "Failed for password: {password}"
            );
        }
    }

    #[test]
    fn test_username_regex_compilation() {
        // Test that the regex compiles and works as expected
        assert!(USERNAME_REGEX.is_match("valid_user"));
        assert!(!USERNAME_REGEX.is_match("invalid@user"));
    }
}