entroll-core 0.1.2

Generate random passwords and print entropy in bits.
Documentation
pub mod ranges;
pub use crate::ranges::Ranges;

/// `Faces` is a trait defining behavior for objects that have a set number of faces, like dice.
/// It provides methods to calculate the entropy based on the number of faces.
#[allow(clippy::len_without_is_empty)]
pub trait Faces {
    /// Returns the number of faces.
    fn len(&self) -> usize;

    /// Calculates the entropy in bits for one face.
    fn entropy_bits_per_face(&self) -> f64 {
        (self.len() as f64).log2()
    }

    fn entropy_bits(&self, n: usize) -> f64 {
        self.entropy_bits_per_face() * (n as f64)
    }

    fn needs_faces(&self, entropy_bits: f64) -> usize {
        (entropy_bits / self.entropy_bits_per_face()).ceil() as usize
    }

    fn needs_length_as_bytes(&self, entropy_bits: f64) -> usize {
        (entropy_bits / self.entropy_bits_per_face() / 8.).ceil() as usize
    }
}

impl<T> Faces for [T] {
    fn len(&self) -> usize {
        self.len()
    }
}

impl<T, const N: usize> Faces for [T; N] {
    fn len(&self) -> usize {
        N
    }
}

impl<T> Faces for Vec<T> {
    fn len(&self) -> usize {
        self.len()
    }
}

pub struct Rollable<T>
where
    T: Faces + core::ops::Index<usize>,
{
    inner: T,
}

impl<T> Default for Rollable<T>
where
    T: Default + Faces + core::ops::Index<usize>,
{
    fn default() -> Self {
        Self {
            inner: T::default(),
        }
    }
}

impl<T> Rollable<T>
where
    T: Faces + core::ops::Index<usize>,
{
    pub fn new(inner: T) -> Self {
        Self { inner }
    }
}

impl<T> Faces for Rollable<T>
where
    T: Faces + core::ops::Index<usize>,
{
    fn len(&self) -> usize {
        self.inner.len()
    }
}

impl<T> rand::distributions::Distribution<<T as core::ops::Index<usize>>::Output> for Rollable<T>
where
    T: Faces + std::ops::Index<usize>,
    T::Output: Copy,
{
    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> T::Output {
        let idx = rng.gen_range(0..self.inner.len());
        self.inner[idx]
    }
}

/// `Dice` struct represents an implementation of dice, using a generic `Faces` collection and RNG.
pub struct Dice<F, Rng>
where
    F: Faces + std::ops::Index<usize>,
    Rng: rand::CryptoRng,
{
    faces: Rollable<F>,
    rng: Rng,
}

impl<F, Rng> Dice<F, Rng>
where
    F: Faces + std::ops::Index<usize>,
    Rng: rand::CryptoRng + rand::Rng,
{
    pub fn new(faces: F, rng: Rng) -> Self {
        Self {
            faces: Rollable::new(faces),
            rng,
        }
    }
}

impl<F, Rng> Dice<F, Rng>
where
    F: Faces + std::ops::Index<usize>,
    F::Output: Copy,
    Rng: rand::CryptoRng + rand::Rng,
{
    pub fn roll(&mut self) -> <F as std::ops::Index<usize>>::Output {
        use rand::distributions::Distribution;
        self.faces.sample(&mut self.rng)
    }
}

impl<F, Rng> Faces for Dice<F, Rng>
where
    F: Faces + std::ops::Index<usize>,
    Rng: rand::CryptoRng + rand::Rng,
{
    fn len(&self) -> usize {
        self.faces.len()
    }
}

pub trait ToDice: Faces
where
    Self: std::ops::Index<usize> + Sized,
    <Self as std::ops::Index<usize>>::Output: Sized,
{
    fn to_dice<Rng>(self, rng: Rng) -> Dice<Self, Rng>
    where
        Self: Sized,
        Rng: rand::CryptoRng + rand::Rng,
    {
        Dice::new(self, rng)
    }

    fn dice(self) -> Dice<Self, rand::rngs::ThreadRng> {
        self.to_dice(rand::thread_rng())
    }
}

impl<T> ToDice for T
where
    T: Faces + std::ops::Index<usize> + Sized,
    <T as std::ops::Index<usize>>::Output: Sized,
{
}

/// Non ascii charactor error.
#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
#[error("Invalid charactor")]
pub struct InvalidCharactor;

/// AsciiFaces is a type that contains ascii charactors.
#[derive(Debug, Clone)]
pub struct AsciiFaces {
    inner: Vec<u8>,
}

impl AsciiFaces {
    fn try_from_iter<I: Iterator<Item = char>>(iter: I) -> Result<Self, InvalidCharactor> {
        iter.map(|c| {
            if c.is_ascii() {
                Ok(c as u8)
            } else {
                Err(InvalidCharactor)
            }
        })
        .collect()
    }

    pub fn contains(&self, c: u8) -> bool {
        self.inner.contains(&c)
    }
}

impl std::iter::FromIterator<u8> for AsciiFaces {
    fn from_iter<I: IntoIterator<Item = u8>>(iter: I) -> Self {
        Self {
            inner: iter.into_iter().collect(),
        }
    }
}

/* This is implemented in CompactWrapper
impl std::convert::TryFrom<&str> for AsciiFaces;
*/

impl<T: Compact> std::convert::TryFrom<CompactWrapper<'_, T>> for AsciiFaces {
    type Error = InvalidCharactor;

    fn try_from(compact: CompactWrapper<T>) -> Result<Self, Self::Error> {
        use ranges::Ranges;
        Self::try_from_iter(compact.compact().chars().ranges())
    }
}

impl std::ops::Index<usize> for AsciiFaces {
    type Output = u8;
    fn index(&self, idx: usize) -> &u8 {
        &self.inner[idx]
    }
}

impl Faces for AsciiFaces {
    fn len(&self) -> usize {
        self.inner.len()
    }
}

/// Compact form. using ranges.
/// like a-z, A-Z, 0-9, etc.
pub trait Compact {
    fn compact(&self) -> &str;

    fn ranges(&self) -> CompactWrapper<'_, &str> {
        CompactWrapper {
            inner: self.compact(),
            _marker: std::marker::PhantomData,
        }
    }
}

impl<S: AsRef<str>> Compact for S {
    fn compact(&self) -> &str {
        self.as_ref()
    }
}

pub struct CompactWrapper<'a, T: 'a>
where
    T: Compact,
{
    inner: T,
    _marker: std::marker::PhantomData<&'a T>,
}

impl<T: Compact> Compact for CompactWrapper<'_, T> {
    fn compact(&self) -> &str {
        self.inner.compact()
    }
}

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

    #[test]
    fn test_faces() {
        let v = vec![1, 2];
        assert_eq!(Faces::len(&v), 2);
    }

    #[test]
    fn test_dice() {
        let v = vec![1, 2];
        let mut dice = v.clone().dice();
        assert!(v.contains(&dice.roll()));
        assert!(v.contains(&dice.roll()));
        assert!(v.contains(&dice.roll()));

        let v = vec!["test", "test2"];
        let mut dice = v.clone().dice();
        assert!(v.contains(&dice.roll()));
        assert!(v.contains(&dice.roll()));
        assert!(v.contains(&dice.roll()));
    }

    #[test]
    fn test_str() {
        let v = "12";
        let mut dice = v.chars().collect::<Vec<char>>().dice();
        assert!(v.contains(dice.roll()));
        assert_eq!(dice.entropy_bits_per_face(), 1.);
    }

    #[test]
    fn test_compact() {
        assert!(AsciiFaces::try_from("a-z".ranges()).is_ok());
        assert!(AsciiFaces::try_from("a-z".ranges()).unwrap().contains(b'g'));
        assert!(!AsciiFaces::try_from("a-z".ranges()).unwrap().contains(b'G'));
    }
}