der_die_das 0.5.0

der_die_das: Learn german genders like a true geek.
Documentation
use std::fmt::Display;

const ALPHABET: [char; 36] = [
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
    'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
];

const LENGHT: usize = 5;

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash)]
pub struct ID(String);

impl AsRef<String> for ID {
    fn as_ref(&self) -> &String {
        &self.0
    }
}

impl Display for ID {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl TryFrom<&str> for ID {
    type Error = miette::Report;

    fn try_from(id: &str) -> Result<Self, Self::Error> {
        if id.len() != LENGHT {
            return Err(miette::miette!(
                "the lenght of id should be 5 character, instead it was {}. the id was {id}.",
                id.len()
            ));
        }

        id.chars().try_for_each(|ch| {
            if !ALPHABET.contains(&ch) {
                return Err(miette::miette!(
                    "The id is in valid. It should not have this character: {ch}, the full id {id}",
                ));
            }
            Ok(())
        })?;
        Ok(Self(id.to_string()))
    }
}

impl TryFrom<String> for ID {
    type Error = miette::Report;

    fn try_from(id: String) -> Result<Self, Self::Error> {
        let id_s = ID::try_from(id.as_str())?;
        Ok(id_s)
    }
}

impl ID {
    pub fn new() -> Self {
        let id = nanoid::nanoid!(LENGHT, &ALPHABET);
        Self(id)
    }
}

impl Default for ID {
    fn default() -> Self {
        Self::new()
    }
}

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

    use super::*;

    #[test]
    fn new_id_test() {
        for i in 0..=1 {
            let ids: Vec<_> = (0..5_000).map(|_| ID::new()).collect();

            let mut ids_dedup = ids.clone();

            ids_dedup.dedup();

            assert_eq!(ids.len(), ids_dedup.len(), "{i}");
        }
    }

    #[rstest]
    #[case::no_letter("", false)]
    #[case::short_lenght("hell", false)]
    #[case::long_lenght("hell00", false)]
    #[case::invalid_characters1("Hell0", false)]
    #[case::invalid_characters2("@ell0", false)]
    #[case::invalid_characters3("_ell0", false)]
    #[case::valid("hell0", true)]
    #[case::valid("hello", true)]
    #[case::valid("12384", true)]
    fn validate_id(#[case] input: &str, #[case] should_be_valid: bool) {
        if should_be_valid {
            assert!(ID::try_from(input).is_ok());
        } else {
            assert!(ID::try_from(input).is_err());
        }
    }
}