rs-password-utils 0.2.1

Password utilities, writen in Rust
Documentation
// Copyright 2020 astonbitecode
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use rand::prelude::*;

mod diceware8k;

/// Generates a passphrase, defining the amount of words that comprise it.
///
/// The words are separated with spaces. (Separation with spaces is proposed [here](http://world.std.com/~reinhold/dicewarefaq.html#spaces))
pub fn generate(size: usize) -> String {
    generate_passphrase(size, None)
}

/// Generates a passphrase by defining a separator to be used between the words.
///
/// size is the amount of words that comprise the passphrase.
pub fn generate_with_separator(size: usize, separator: &str) -> String {
    generate_passphrase(size, separator)
}

fn generate_passphrase<'a, SEP>(size: usize, separator: SEP) -> String
where
    SEP: Into<Option<&'a str>>,
{
    let separator = separator.into().unwrap_or(" ");
    if size <= 0 {
        "".to_string()
    } else {
        (0..size)
            .map(|_| {
                let mut buf = [0u8; 16];
                let mut rng = rand::rng();
                rng.fill_bytes(&mut buf);
                let random_u64 = rng.random::<u64>();
                diceware8k::get_dice_word(random_u64 as usize)
            })
            .fold("".to_string(), |mut acc, passphrase| {
                if !acc.is_empty() {
                    acc.push_str(separator);
                }
                acc.push_str(passphrase);
                acc
            })
    }
}

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

    #[test]
    fn test_generate_passphrase() {
        assert!(generate_passphrase(0, None) == "");
        let mut all: Vec<String> = Vec::new();

        for _ in 0..1000 {
            let passphrase = generate_passphrase(10, None);
            if all.contains(&passphrase) {
                assert!(
                    false,
                    "Same passphrase has already been generated: {}",
                    passphrase
                );
            }
            all.push(passphrase);
        }

        let passphrase = generate_passphrase(3, "~");
        let s: Vec<&str> = passphrase.split('~').collect();
        assert_eq!(s.len(), 3);
    }

    #[test]
    fn test_generate_with_separator() {
        let passphrase = generate_with_separator(3, "~");
        let s: Vec<&str> = passphrase.split('~').collect();
        assert_eq!(s.len(), 3);

        let passphrase = generate_with_separator(3, "");
        let s: Vec<&str> = passphrase.split(" ").collect();
        assert_eq!(s.len(), 1);
    }

    #[test]
    fn test_generate() {
        let passphrase = generate_with_separator(3, " ");
        let s: Vec<&str> = passphrase.split(' ').collect();
        assert_eq!(s.len(), 3);
    }
}