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());
}
}
}