lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

username.rs (2395B)


      1 #![forbid(unsafe_code)]
      2 
      3 #[cfg(not(feature = "std"))]
      4 use alloc::string::String;
      5 
      6 pub const RADROOTS_USERNAME_MIN_LEN: usize = 3;
      7 pub const RADROOTS_USERNAME_MAX_LEN: usize = 30;
      8 pub const RADROOTS_USERNAME_REGEX: &str = r"^(?!.*\.\.)(?!\.)(?!.*\.$)[a-z0-9._-]{3,30}$";
      9 
     10 pub fn radroots_username_is_valid(username: &str) -> bool {
     11     if !username.is_ascii() {
     12         return false;
     13     }
     14     let len = username.len();
     15     if !(RADROOTS_USERNAME_MIN_LEN..=RADROOTS_USERNAME_MAX_LEN).contains(&len) {
     16         return false;
     17     }
     18     let bytes = username.as_bytes();
     19     if bytes.first() == Some(&b'.') || bytes.last() == Some(&b'.') {
     20         return false;
     21     }
     22     let mut prev_dot = false;
     23     for &byte in bytes {
     24         if byte == b'.' {
     25             if prev_dot {
     26                 return false;
     27             }
     28             prev_dot = true;
     29             continue;
     30         }
     31         prev_dot = false;
     32         let is_alpha = byte.is_ascii_lowercase();
     33         let is_digit = byte.is_ascii_digit();
     34         let is_allowed = is_alpha || is_digit || byte == b'_' || byte == b'-';
     35         if !is_allowed {
     36             return false;
     37         }
     38     }
     39     true
     40 }
     41 
     42 pub fn radroots_username_normalize(input: &str) -> Option<String> {
     43     let trimmed = input.trim();
     44     if trimmed.is_empty() {
     45         return None;
     46     }
     47     let normalized = trimmed.to_ascii_lowercase();
     48     if radroots_username_is_valid(&normalized) {
     49         Some(normalized)
     50     } else {
     51         None
     52     }
     53 }
     54 
     55 #[cfg(all(test, feature = "std"))]
     56 mod tests {
     57     use super::*;
     58 
     59     #[test]
     60     fn valid_usernames() {
     61         for name in ["radroots", "radroots_1", "radroots.test", "rr-01"] {
     62             assert!(radroots_username_is_valid(name));
     63         }
     64     }
     65 
     66     #[test]
     67     fn invalid_usernames() {
     68         for name in [
     69             "ra",
     70             ".radroots",
     71             "radroots.",
     72             "radroots..test",
     73             "radroots!",
     74             "RADROOTS",
     75             "rädroots",
     76             "radroots-radroots-radroots-radroots",
     77         ] {
     78             assert!(!radroots_username_is_valid(name));
     79         }
     80     }
     81 
     82     #[test]
     83     fn normalize_usernames() {
     84         assert_eq!(
     85             radroots_username_normalize("  RadRoots  "),
     86             Some("radroots".to_string())
     87         );
     88         assert_eq!(radroots_username_normalize("ra"), None);
     89         assert_eq!(radroots_username_normalize("   "), None);
     90     }
     91 }