user_name.rs 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. use crate::errors::ErrorCode;
  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, ErrorCode> {
  7. let is_empty_or_whitespace = s.trim().is_empty();
  8. if is_empty_or_whitespace {
  9. return Err(ErrorCode::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(ErrorCode::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(ErrorCode::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. use claim::{assert_err, assert_ok};
  39. #[test]
  40. fn a_256_grapheme_long_name_is_valid() {
  41. let name = "a̐".repeat(256);
  42. assert_ok!(UserName::parse(name));
  43. }
  44. #[test]
  45. fn a_name_longer_than_256_graphemes_is_rejected() {
  46. let name = "a".repeat(257);
  47. assert_err!(UserName::parse(name));
  48. }
  49. #[test]
  50. fn whitespace_only_names_are_rejected() {
  51. let name = " ".to_string();
  52. assert_err!(UserName::parse(name));
  53. }
  54. #[test]
  55. fn empty_string_is_rejected() {
  56. let name = "".to_string();
  57. assert_err!(UserName::parse(name));
  58. }
  59. #[test]
  60. fn names_containing_an_invalid_character_are_rejected() {
  61. for name in &['/', '(', ')', '"', '<', '>', '\\', '{', '}'] {
  62. let name = name.to_string();
  63. assert_err!(UserName::parse(name));
  64. }
  65. }
  66. #[test]
  67. fn a_valid_name_is_parsed_successfully() {
  68. let name = "nathan".to_string();
  69. assert_ok!(UserName::parse(name));
  70. }
  71. }