use unicode_segmentation::UnicodeSegmentation; #[derive(Debug)] pub struct SubscriberName(String); impl SubscriberName { pub fn parse(s: String) -> Result { let is_empty_or_whitespace = s.trim().is_empty(); let is_too_long = s.graphemes(true).count() > 256; let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}']; let contains_forbidden_characters = s.chars().any(|g| forbidden_characters.contains(&g)); if is_empty_or_whitespace || is_too_long || contains_forbidden_characters { Err(format!("{} is not a valid subscriber name.", s)) } else { Ok(Self(s)) } } } impl AsRef for SubscriberName { fn as_ref(&self) -> &str { self.0.as_str() } } #[cfg(test)] mod tests { use crate::domain::SubscriberName; use claims::{assert_err, assert_ok}; #[test] fn a_256_grapheme_long_name_is_valid() { let name = "ê".repeat(256); assert_ok!(SubscriberName::parse(name)); } #[test] fn a_name_longer_than_256_graphemes_is_rejected() { let name = "ê".repeat(257); assert_err!(SubscriberName::parse(name)); } #[test] fn a_whitespace_only_name_is_rejected() { let name = "\n \t ".to_string(); assert_err!(SubscriberName::parse(name)); } #[test] fn empty_string_is_rejected() { let name = "".to_string(); assert_err!(SubscriberName::parse(name)); } #[test] fn a_name_containing_invalid_character_is_rejected() { for name in ['/', '(', ')', '"', '<', '>', '\\', '{', '}'] { let name = name.to_string(); assert_err!(SubscriberName::parse(name)); } } #[test] fn a_valid_name_is_parsed_successfully() { let name = "Alphonse".to_string(); assert_ok!(SubscriberName::parse(name)); } }