use super::Error; use nanoid::nanoid; use nanoid_dictionary::NOLOOKALIKES; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use std::str::FromStr; const LENGTH: usize = 10; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct AppID(pub String); impl Display for AppID { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } impl Default for AppID { fn default() -> Self { Self(nanoid!(LENGTH, NOLOOKALIKES)) } } impl AsRef for AppID { fn as_ref(&self) -> &str { self.0.as_ref() } } impl FromStr for AppID { type Err = Error; fn from_str(s: &str) -> Result { if s == "ezidam" || s.len() == LENGTH && s.chars().all(|c| NOLOOKALIKES.contains(&c)) { Ok(Self(s.to_string())) } else { Err(Error::Invalid("App")) } } } #[cfg(test)] mod tests { use super::{AppID, LENGTH}; use std::str::FromStr; #[test] fn invalid_length() { assert!(AppID::from_str("test").is_err()); } #[test] fn invalid_characters() { let value = "1I0ov5Ss2Z"; assert_eq!(value.len(), LENGTH); assert!(AppID::from_str(value).is_err()); } #[test] fn valid_id() { let value = "nqxTGaXUgn"; let id = AppID::from_str(value); assert!(id.is_ok()); let id = id.unwrap(); assert_eq!(id.0, value); } #[test] fn valid_ezidam_id() { let value = "ezidam"; let id = AppID::from_str(value); assert!(id.is_ok()); let id = id.unwrap(); assert_eq!(id.0, value); } }