1
package who
2
3
import "regexp"
4
import "strings"
5
6
// UsernameRegex matches a primary //who username: starts with a
7
// lowercase letter, then up to 31 more lowercase letters,
8
// digits, hyphens, or underscores.
9
const UsernameRegex = `[a-z][-a-z0-9_]{0,31}`
10
11
// usernamePattern is the compiled, anchored form of
12
// UsernameRegex, used by IsValidUsername.
13
var usernamePattern = regexp.MustCompile(
14
`^` + UsernameRegex + `$`)
15
16
// IsValidUsername returns true if the username matches
17
// UsernameRegex.
18
func IsValidUsername(username string) bool {
19
return usernamePattern.MatchString(username)
20
}
21
22
// FullUsernameRegex matches both regular usernames and
23
// sub-usernames (parent.child).
24
const FullUsernameRegex = UsernameRegex +
25
`(\.` + UsernameRegex + `)?`
26
27
// IsSubUsername returns true if username is a valid sub-username
28
// (contains exactly one dot, both parts valid).
29
func IsSubUsername(username string) bool {
30
dot := strings.IndexByte(username, '.')
31
if dot < 0 {
32
return false
33
}
34
return IsValidUsername(username[:dot]) &&
35
IsValidUsername(username[dot+1:])
36
}
37
38
// ParentUsername returns the parent portion of a sub-username,
39
// or "" if not a sub-username.
40
func ParentUsername(username string) string {
41
dot := strings.IndexByte(username, '.')
42
if dot < 0 {
43
return ""
44
}
45
return username[:dot]
46
}
1
package who
2
3
import "testing"
4
5
func TestIsValidUsername(t *testing.T) {
6
check := func(in string, want bool) {
7
t.Helper()
8
if got := IsValidUsername(in); got != want {
9
t.Errorf("IsValidUsername(%q): got %v, want %v",
10
in, got, want)
11
}
12
}
13
check("alice", true)
14
check("a", true)
15
check("a-b", true)
16
check("a_b", true)
17
check("a123", true)
18
check("", false)
19
check("Alice", false) // uppercase
20
check("1alice", false) // starts with digit
21
check("alice.bob", false) // dot (sub-username)
22
check("alice@bob", false) // disallowed char
23
check("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", false) // 33 chars
24
}
25
26
func TestIsSubUsername(t *testing.T) {
27
check := func(in string, want bool) {
28
t.Helper()
29
if got := IsSubUsername(in); got != want {
30
t.Errorf("IsSubUsername(%q): got %v, want %v",
31
in, got, want)
32
}
33
}
34
check("alice.bob", true)
35
check("alice", false)
36
check("alice.bob.charlie", false) // two dots
37
check(".bob", false)
38
check("alice.", false)
39
}
40
41
func TestParentUsername(t *testing.T) {
42
check := func(in, want string) {
43
t.Helper()
44
if got := ParentUsername(in); got != want {
45
t.Errorf("ParentUsername(%q): got %q, want %q",
46
in, got, want)
47
}
48
}
49
check("alice.bob", "alice")
50
check("alice", "")
51
}