hubworld 0.3.1

A Rust library for generating random data: UUIDs, names, passwords, and credit cards
Documentation
use crate::credit_card::CreditCardOutputFormat;
use rand::Rng;

const FIRST_NAME_STARTS: &[&str] = &[
    "Al", "An", "Br", "Ch", "Da", "Ed", "El", "Er", "Fr", "Ge", "Ja", "Jo", "Ka", "Ke", "La", "Ma",
    "Mi", "Ni", "Pa", "Ra", "Ri", "Ro", "Sa", "Sh", "St", "Ta", "Th", "Tr", "Wi", "Za",
];

const FIRST_NAME_MIDDLES: &[&str] = &[
    "an", "ar", "el", "en", "er", "es", "ic", "in", "is", "it", "on", "or", "ow", "un", "us", "al",
    "am", "at", "ed", "ol",
];

const FIRST_NAME_ENDS: &[&str] = &[
    "a", "an", "el", "en", "er", "es", "ia", "ic", "ie", "in", "is", "on", "or", "us", "yn", "ah",
    "am", "as", "ed", "em", "ey", "im", "le", "ol", "ry", "ty",
];

const LAST_NAME_STARTS: &[&str] = &[
    "Ba", "Be", "Br", "Ca", "Ch", "Cl", "Co", "Cr", "Da", "De", "Di", "Do", "Dr", "El", "Ev", "Fa",
    "Fi", "Fr", "Ga", "Ge", "Gi", "Gr", "Ha", "He", "Hi", "Ho", "Hu", "Ja", "Jo", "Ka", "Ke", "Ki",
    "Kn", "La", "Le", "Li", "Lo", "Lu", "Ma", "Mc", "Me", "Mi", "Mo", "Mu", "Na", "Ne", "Ni", "No",
    "O'", "Ol", "Or", "Pa", "Pe", "Ph", "Pi", "Po", "Pr", "Qu", "Ra", "Re", "Ri", "Ro", "Ru", "Sa",
    "Sc", "Se", "Sh", "Si", "Sk", "Sl", "Sm", "Sn", "So", "Sp", "St", "Su", "Sw", "Ta", "Te", "Th",
    "Ti", "To", "Tr", "Tu", "Ty", "Un", "Va", "Vi", "Wa", "We", "Wh", "Wi", "Wo", "Wr", "Ya", "Ye",
    "Yo", "Za",
];

const LAST_NAME_MIDDLES: &[&str] = &[
    "al", "an", "ar", "as", "at", "ay", "el", "en", "er", "es", "et", "ey", "ic", "id", "il", "in",
    "is", "it", "le", "li", "lo", "ly", "ma", "me", "mi", "mo", "na", "ne", "ni", "no", "ol", "on",
    "or", "os", "ot", "ow", "ra", "re", "ri", "ro", "ry", "sa", "se", "si", "so", "st", "ta", "te",
    "ti", "to", "tr", "ty", "ul", "un", "ur", "us", "ut", "wa", "we", "wi", "wo", "ya", "ye", "yo",
];

const LAST_NAME_ENDS: &[&str] = &[
    "an", "ar", "as", "ay", "ed", "el", "en", "er", "es", "et", "ey", "ia", "ic", "id", "ie", "in",
    "is", "it", "le", "ly", "ma", "me", "mi", "mo", "na", "ne", "ni", "no", "ns", "nt", "ol", "om",
    "on", "or", "os", "ot", "ow", "ra", "rd", "re", "rk", "rl", "rm", "rn", "ro", "rs", "rt", "ry",
    "se", "sh", "si", "sk", "sm", "sn", "so", "sp", "ss", "st", "te", "th", "ti", "to", "ts", "ty",
    "ul", "un", "ur", "us", "ut", "wa", "we", "wi", "wn", "ws", "ya", "ye", "yo", "ys", "za",
];

/// Generates a natural-sounding first name using syllable combinations
pub fn generate_first_name() -> String {
    let mut rng = rand::thread_rng();
    let mut name = String::new();

    name.push_str(FIRST_NAME_STARTS[rng.gen_range(0..FIRST_NAME_STARTS.len())]);

    if rng.gen_bool(0.7) {
        name.push_str(FIRST_NAME_MIDDLES[rng.gen_range(0..FIRST_NAME_MIDDLES.len())]);
    }

    if rng.gen_bool(0.4) && name.len() < 8 {
        name.push_str(FIRST_NAME_MIDDLES[rng.gen_range(0..FIRST_NAME_MIDDLES.len())]);
    }

    name.push_str(FIRST_NAME_ENDS[rng.gen_range(0..FIRST_NAME_ENDS.len())]);

    name = name
        .chars()
        .enumerate()
        .map(|(i, c)| {
            if i == 0 {
                c.to_uppercase().collect::<String>()
            } else {
                c.to_lowercase().collect::<String>()
            }
        })
        .collect();

    name
}

/// Generates a first name in the selected format.
pub fn generate_first_name_formatted(format: CreditCardOutputFormat) -> String {
    let first_name = generate_first_name();
    match format {
        CreditCardOutputFormat::Plain => first_name,
        CreditCardOutputFormat::Json => format!("{{\"first_name\":\"{}\"}}", first_name),
        CreditCardOutputFormat::JsonLd => format!(
            "{{\"@context\":\"https://schema.org\",\"@type\":\"Person\",\"givenName\":\"{}\"}}",
            first_name
        ),
    }
}

/// Generates a natural-sounding last name using syllable combinations
pub fn generate_last_name() -> String {
    let mut rng = rand::thread_rng();
    let mut name = String::new();

    name.push_str(LAST_NAME_STARTS[rng.gen_range(0..LAST_NAME_STARTS.len())]);

    if rng.gen_bool(0.8) {
        name.push_str(LAST_NAME_MIDDLES[rng.gen_range(0..LAST_NAME_MIDDLES.len())]);
    }

    if rng.gen_bool(0.5) && name.len() < 10 {
        name.push_str(LAST_NAME_MIDDLES[rng.gen_range(0..LAST_NAME_MIDDLES.len())]);
    }

    name.push_str(LAST_NAME_ENDS[rng.gen_range(0..LAST_NAME_ENDS.len())]);

    if name.starts_with("Mc") || name.starts_with("O'") {
        let mut chars: Vec<char> = name.chars().collect();
        if !chars.is_empty() {
            chars[0] = chars[0].to_uppercase().next().unwrap();
        }
        if chars.len() > 2 {
            chars[2] = chars[2].to_uppercase().next().unwrap();
        }
        for (i, char_ref) in chars.iter_mut().enumerate().skip(1) {
            if i != 2 {
                *char_ref = char_ref.to_lowercase().next().unwrap();
            }
        }
        name = chars.into_iter().collect();
    } else {
        name = name
            .chars()
            .enumerate()
            .map(|(i, c)| {
                if i == 0 {
                    c.to_uppercase().collect::<String>()
                } else {
                    c.to_lowercase().collect::<String>()
                }
            })
            .collect();
    }

    name
}

/// Generates a last name in the selected format.
pub fn generate_last_name_formatted(format: CreditCardOutputFormat) -> String {
    let last_name = generate_last_name();
    match format {
        CreditCardOutputFormat::Plain => last_name,
        CreditCardOutputFormat::Json => format!("{{\"last_name\":\"{}\"}}", last_name),
        CreditCardOutputFormat::JsonLd => format!(
            "{{\"@context\":\"https://schema.org\",\"@type\":\"Person\",\"familyName\":\"{}\"}}",
            last_name
        ),
    }
}

/// Generates a random full name (first name + last name)
pub fn generate_full_name() -> String {
    format!("{} {}", generate_first_name(), generate_last_name())
}

/// Generates a full name in the selected format.
pub fn generate_full_name_formatted(format: CreditCardOutputFormat) -> String {
    let first_name = generate_first_name();
    let last_name = generate_last_name();
    let full_name = format!("{} {}", first_name, last_name);

    match format {
        CreditCardOutputFormat::Plain => full_name,
        CreditCardOutputFormat::Json => format!(
            "{{\"first_name\":\"{}\",\"last_name\":\"{}\",\"full_name\":\"{}\"}}",
            first_name, last_name, full_name
        ),
        CreditCardOutputFormat::JsonLd => format!(
            "{{\"@context\":\"https://schema.org\",\"@type\":\"Person\",\"givenName\":\"{}\",\"familyName\":\"{}\",\"name\":\"{}\"}}",
            first_name, last_name, full_name
        ),
    }
}

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

    #[test]
    fn test_generate_first_name() {
        let name = generate_first_name();
        assert!(!name.is_empty());
        assert!(name.len() >= 2);
        assert!(name.chars().next().unwrap().is_uppercase());
    }

    #[test]
    fn test_generate_last_name() {
        let name = generate_last_name();
        assert!(!name.is_empty());
        assert!(name.len() >= 2);
        assert!(name.chars().next().unwrap().is_uppercase());
    }

    #[test]
    fn test_generate_full_name() {
        let name = generate_full_name();
        let parts: Vec<&str> = name.split_whitespace().collect();
        assert_eq!(parts.len(), 2);
        assert!(!parts[0].is_empty());
        assert!(!parts[1].is_empty());
    }

    #[test]
    fn test_multiple_names_are_different() {
        let name1 = generate_full_name();
        let name2 = generate_full_name();
        let name3 = generate_full_name();

        let all_same = name1 == name2 && name2 == name3;
        assert!(!all_same);
    }

    #[test]
    fn test_name_generation_produces_variety() {
        let mut names = Vec::new();
        for _ in 0..20 {
            names.push(generate_first_name());
        }

        let unique_count = names.iter().collect::<std::collections::HashSet<_>>().len();
        assert!(unique_count > 5, "Should generate variety of names");
    }

    #[test]
    fn test_generate_first_name_formatted_json() {
        let output = generate_first_name_formatted(CreditCardOutputFormat::Json);
        assert!(output.contains("\"first_name\":\""));
    }

    #[test]
    fn test_generate_last_name_formatted_json() {
        let output = generate_last_name_formatted(CreditCardOutputFormat::Json);
        assert!(output.contains("\"last_name\":\""));
    }

    #[test]
    fn test_generate_full_name_formatted_jsonld() {
        let output = generate_full_name_formatted(CreditCardOutputFormat::JsonLd);
        assert!(output.contains("\"@context\":\"https://schema.org\""));
        assert!(output.contains("\"@type\":\"Person\""));
        assert!(output.contains("\"name\":\""));
    }
}