···66 "strings"
77)
8899+var handleRegex = regexp.MustCompile(`^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$`)
1010+911// String type which represents a syntaxtually valid handle identifier, as would pass Lexicon syntax validation.
1012//
1113// Always use [ParseHandle] instead of wrapping strings directly, especially when working with input.
···1719 if len(raw) > 253 {
1820 return "", fmt.Errorf("handle is too long (253 chars max)")
1921 }
2020- var handleRegex = regexp.MustCompile(`^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$`)
2122 if !handleRegex.MatchString(raw) {
2223 return "", fmt.Errorf("handle syntax didn't validate via regex")
2324 }
···66 "strings"
77)
8899+var nsidRegex = regexp.MustCompile(`^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z]([a-zA-Z]{0,61}[a-zA-Z])?)$`)
1010+911// String type which represents a syntaxtually valid Namespace Identifier (NSID), as would pass Lexicon syntax validation.
1012//
1113// Always use [ParseNSID] instead of wrapping strings directly, especially when working with input.
···1719 if len(raw) > 317 {
1820 return "", fmt.Errorf("NSID is too long (317 chars max)")
1921 }
2020- var nsidRegex = regexp.MustCompile(`^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z]([a-zA-Z]{0,61}[a-zA-Z])?)$`)
2122 if !nsidRegex.MatchString(raw) {
2223 return "", fmt.Errorf("NSID syntax didn't validate via regex")
2324 }
···55 "regexp"
66)
7788+var recordKeyRegex = regexp.MustCompile(`^[a-zA-Z0-9_~.-]{1,512}$`)
99+810// String type which represents a syntaxtually valid RecordKey identifier, as could be included in an AT URI
911//
1012// Always use [ParseRecordKey] instead of wrapping strings directly, especially when working with input.
···1315type RecordKey string
14161517func ParseRecordKey(raw string) (RecordKey, error) {
1616- if len(raw) > 253 {
1818+ if len(raw) > 512 {
1719 return "", fmt.Errorf("recordkey is too long (512 chars max)")
1820 }
1921 if raw == "" || raw == "." || raw == ".." {
2022 return "", fmt.Errorf("recordkey can not be empty, '.', or '..'")
2123 }
2222- var recordkeyRegex = regexp.MustCompile(`^[a-zA-Z0-9_~.-]{1,512}$`)
2323- if !recordkeyRegex.MatchString(raw) {
2424+ if !recordKeyRegex.MatchString(raw) {
2425 return "", fmt.Errorf("recordkey syntax didn't validate via regex")
2526 }
2627 return RecordKey(raw), nil
2728}
28292929-func (h RecordKey) String() string {
3030- return string(h)
3030+func (r RecordKey) String() string {
3131+ return string(r)
3132}
···3232john.-est
3333john.tes-
34343535-# TODO: short/long examples
3535+# max over all handle: 'shoooort' + '.loooooooooooooooooooooooooong'.repeat(9) + '.test'
3636+shoooort.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.test
3737+3838+# max segment: 'short.' + 'o'.repeat(64) + '.test'
3939+short.oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.test
36403741# throws on "dotless" TLD handles
3842org
+6-1
atproto/syntax/testdata/handle_syntax_valid.txt
···1616jaymome-johnber123456.test
1717jay.mome-johnber123456.test
1818john.test.bsky.app
1919-# TODO: short and long
1919+2020+# max over all handle: 'shoooort' + '.loooooooooooooooooooooooooong'.repeat(8) + '.test'
2121+shoooort.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.test
2222+2323+# max segment: 'short.' + 'o'.repeat(63) + '.test'
2424+short.ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.test
20252126# NOTE: this probably isn't ever going to be a real domain, but my read of the RFC is that it would be possible
2227john.t