user_name.rs 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. use crate::errors::UserErrorCode;
  2. use unicode_segmentation::UnicodeSegmentation;
  3. #[derive(Debug)]
  4. pub struct UserName(pub String);
  5. impl UserName {
  6. pub fn parse(s: String) -> Result<UserName, UserErrorCode> {
  7. let is_empty_or_whitespace = s.trim().is_empty();
  8. if is_empty_or_whitespace {
  9. return Err(UserErrorCode::UserNameIsEmpty);
  10. }
  11. // A grapheme is defined by the Unicode standard as a "user-perceived"
  12. // character: `å` is a single grapheme, but it is composed of two characters
  13. // (`a` and `̊`).
  14. //
  15. // `graphemes` returns an iterator over the graphemes in the input `s`.
  16. // `true` specifies that we want to use the extended grapheme definition set,
  17. // the recommended one.
  18. let is_too_long = s.graphemes(true).count() > 256;
  19. if is_too_long {
  20. return Err(UserErrorCode::UserNameTooLong);
  21. }
  22. let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}'];
  23. let contains_forbidden_characters = s.chars().any(|g| forbidden_characters.contains(&g));
  24. if contains_forbidden_characters {
  25. return Err(UserErrorCode::UserNameContainForbiddenCharacters);
  26. }
  27. Ok(Self(s))
  28. }
  29. }
  30. impl AsRef<str> for UserName {
  31. fn as_ref(&self) -> &str {
  32. &self.0
  33. }
  34. }
  35. #[cfg(test)]
  36. mod tests {
  37. use super::UserName;
  38. #[test]
  39. fn a_256_grapheme_long_name_is_valid() {
  40. let name = "a̐".repeat(256);
  41. assert!(UserName::parse(name).is_ok());
  42. }
  43. #[test]
  44. fn a_name_longer_than_256_graphemes_is_rejected() {
  45. let name = "a".repeat(257);
  46. assert!(UserName::parse(name).is_err());
  47. }
  48. #[test]
  49. fn whitespace_only_names_are_rejected() {
  50. let name = " ".to_string();
  51. assert!(UserName::parse(name).is_err());
  52. }
  53. #[test]
  54. fn empty_string_is_rejected() {
  55. let name = "".to_string();
  56. assert!(UserName::parse(name).is_err());
  57. }
  58. #[test]
  59. fn names_containing_an_invalid_character_are_rejected() {
  60. for name in &['/', '(', ')', '"', '<', '>', '\\', '{', '}'] {
  61. let name = name.to_string();
  62. assert!(UserName::parse(name).is_err());
  63. }
  64. }
  65. #[test]
  66. fn a_valid_name_is_parsed_successfully() {
  67. let name = "nathan".to_string();
  68. assert!(UserName::parse(name).is_ok());
  69. }
  70. }