vortex-otp-lib 0.1.4

A flexible One-Time Password (OTP) generation library for Rust.
Documentation
use rand::{rngs::ThreadRng, seq::SliceRandom, Rng};

pub enum OtpCharSet {
    Numeric,
    Alphanumeric,
    AlphanumericSpecialChars,
    Custom(String),
}

pub fn generate_otp(length: usize, char_set_type: OtpCharSet) -> Result<String, &'static str> {
    let mut rng: ThreadRng = rand::thread_rng();

    let chars: Vec<char> = match char_set_type {
        OtpCharSet::Numeric => "0123456789".chars().collect(),
        OtpCharSet::Alphanumeric => "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect(),
        OtpCharSet::AlphanumericSpecialChars => "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=[]{}|;:,.<>?".chars().collect(),
        OtpCharSet::Custom(s) => {
            if s.is_empty() {
                return Err("Custom character set cannot be empty");
            }
            s.chars().collect()
        }
    };

    if chars.is_empty() {
        return Err("Effective character set is empty");
    }

    let otp: String = (0..length)
        .map(|_| {
            *chars
                .choose(&mut rng)
                .expect("Character set should not be empty at this point")
        })
        .collect();

    Ok(otp)
}

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

    #[test]
    fn generate_numeric_otp() {
        let otp = generate_otp(6, OtpCharSet::Numeric).unwrap();
        assert_eq!(otp.len(), 6);
        assert!(otp.chars().all(|c| c.is_digit(10)));
    }

    #[test]
    fn generate_alphanumeric_otp() {
        let otp = generate_otp(8, OtpCharSet::Alphanumeric).unwrap();
        assert_eq!(otp.len(), 8);
        assert!(otp.chars().all(|c| c.is_alphanumeric()));
    }

    #[test]
    fn generate_alphanumeric_special_otp() {
        let otp = generate_otp(10, OtpCharSet::AlphanumericSpecialChars).unwrap();
        assert_eq!(otp.len(), 10);
        assert!(otp
            .chars()
            .any(|c| !c.is_alphanumeric() && !c.is_whitespace()));
    }

    #[test]
    fn generate_custom_otp_valid() {
        let custom_set = "ABC123xyz!@#".to_string();
        let otp = generate_otp(7, OtpCharSet::Custom(custom_set.clone())).unwrap();
        assert_eq!(otp.len(), 7);
        assert!(otp.chars().all(|c| custom_set.contains(c)));
    }

    #[test]
    fn generate_custom_otp_empty_set() {
        let result = generate_otp(5, OtpCharSet::Custom("".to_string()));
        assert!(result.is_err());
        assert_eq!(result.err(), Some("Custom character set cannot be empty"));
    }

    #[test]
    fn generate_zero_length_otp() {
        let otp = generate_otp(0, OtpCharSet::Numeric).unwrap();
        assert_eq!(otp.len(), 0);
        assert_eq!(otp, "");
    }

    #[test]
    fn generate_custom_otp_only_one_char() {
        let custom_set = "A".to_string();
        let otp = generate_otp(5, OtpCharSet::Custom(custom_set.clone())).unwrap();
        assert_eq!(otp.len(), 5);
        assert_eq!(otp, "AAAAA");
    }

    fn add(left: u64, right: u64) -> u64 {
        left + right
    }

    #[test]
    fn test_add_function() {
        assert_eq!(add(2, 2), 4);
    }
}