known_types_github/
handle.rs1#[cfg(not(feature = "alloc"))]
4compile_error!("this module requires the 'alloc' feature");
5
6use alloc::string::String;
7use core::str::FromStr;
8use derive_more::{AsRef, Display};
9pub use known_types::handle::ParseHandleError;
10use known_types::handle::{validate_ascii, validate_length};
11
12#[derive(AsRef, Clone, Debug, Display, Eq)]
35pub struct GithubHandle(String);
36
37known_types::impl_handle!(GithubHandle, 1, 39, "GithubHandle");
38known_types::impl_handle_comparison!(GithubHandle);
39
40impl GithubHandle {
41 fn comparison_key(&self) -> unicase::Ascii<&str> {
42 unicase::Ascii::new(self.as_str())
43 }
44}
45
46impl FromStr for GithubHandle {
47 type Err = ParseHandleError;
48
49 fn from_str(input: &str) -> Result<Self, Self::Err> {
50 let input = input.strip_prefix('@').unwrap_or(input);
51 validate_length(input, Self::MIN_LENGTH, Self::MAX_LENGTH)?;
52 let name = match input.split_once('_') {
53 Some((name, shortcode)) => {
54 validate_length(shortcode, 3, 8)?;
55 validate_ascii(shortcode, "")?;
56 name
57 }
58 None => input,
59 };
60 validate_ascii(name, "-")?;
61 if name.is_empty() || name.starts_with('-') || name.ends_with('-') || name.contains("--") {
62 return Err(ParseHandleError::InvalidFormat);
63 }
64 Ok(Self(input.into()))
65 }
66}
67
68#[test]
69fn test_github_handle_syntax() {
70 for (input, stored) in [
71 ("@Octo-Cat", "Octo-Cat"),
72 ("a", "a"),
73 ("123", "123"),
74 ("@The-Octocat_octo", "The-Octocat_octo"),
75 ("octo_admin", "octo_admin"),
76 ] {
77 assert_eq!(
78 input
79 .parse::<GithubHandle>()
80 .expect("valid handle")
81 .as_str(),
82 stored
83 );
84 }
85 for input in [
86 "@",
87 "@@Octocat",
88 "-octocat",
89 "octocat-",
90 "octo--cat",
91 "octo.cat",
92 "octo cat",
93 "octöcat",
94 "_octo",
95 "octo_",
96 "octo_ab",
97 "octo_abcdefghi",
98 "octo_abc_def",
99 "octo_ab-c",
100 "octocat\n",
101 ] {
102 assert!(input.parse::<GithubHandle>().is_err(), "accepted {input:?}");
103 }
104}