1package syntax
2
3import (
4 "errors"
5 "fmt"
6 "regexp"
7 "strings"
8)
9
10var (
11 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])?$`)
12
13 // special handle string constant indicating that handle resolution failed
14 HandleInvalid = Handle("handle.invalid")
15)
16
17// String type which represents a syntaxtually valid handle identifier, as would pass Lexicon syntax validation.
18//
19// Always use [ParseHandle] instead of wrapping strings directly, especially when working with input.
20//
21// Syntax specification: https://atproto.com/specs/handle
22type Handle string
23
24func ParseHandle(raw string) (Handle, error) {
25 if raw == "" {
26 return "", errors.New("expected handle, got empty string")
27 }
28 if len(raw) > 253 {
29 return "", errors.New("handle is too long (253 chars max)")
30 }
31 if !handleRegex.MatchString(raw) {
32 return "", fmt.Errorf("handle syntax didn't validate via regex: %s", raw)
33 }
34 return Handle(raw), nil
35}
36
37// Some top-level domains (TLDs) are disallowed for registration across the atproto ecosystem. The *syntax* is valid, but these should never be considered acceptable handles for account registration or linking.
38//
39// The reserved '.test' TLD is allowed, for testing and development. It is expected that '.test' domain resolution will fail in a real-world network.
40func (h Handle) AllowedTLD() bool {
41 switch h.TLD() {
42 case "local",
43 "arpa",
44 "invalid",
45 "localhost",
46 "internal",
47 "example",
48 "onion",
49 "alt":
50 return false
51 }
52 return true
53}
54
55func (h Handle) TLD() string {
56 parts := strings.Split(string(h.Normalize()), ".")
57 return parts[len(parts)-1]
58}
59
60// Is this the special "handle.invalid" handle?
61func (h Handle) IsInvalidHandle() bool {
62 return h.Normalize() == HandleInvalid
63}
64
65func (h Handle) Normalize() Handle {
66 return Handle(strings.ToLower(string(h)))
67}
68
69func (h Handle) AtIdentifier() AtIdentifier {
70 return AtIdentifier{Inner: h}
71}
72
73func (h Handle) String() string {
74 return string(h)
75}
76
77func (h Handle) MarshalText() ([]byte, error) {
78 return []byte(h.String()), nil
79}
80
81func (h *Handle) UnmarshalText(text []byte) error {
82 handle, err := ParseHandle(string(text))
83 if err != nil {
84 return err
85 }
86 *h = handle
87 return nil
88}