Something went wrong. Try again.
Go boilerplate library for building atproto apps
atproto go
Something went wrong. Try again.
atp uri.go
2.9 kB · 101 lines
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102package atp
import ( "fmt" "regexp" "strings"
"github.com/bluesky-social/indigo/atproto/syntax" "golang.org/x/net/idna")
// URI holds the parsed components of an AT Protocol URI (at://did/collection/rkey).type URI struct { DID string Collection string RKey string}
// String reconstructs the AT-URI from its components.func (u *URI) String() string { return BuildATURI(u.DID, u.Collection, u.RKey)}
// BuildATURI constructs an AT-URI string from its components.func BuildATURI(did, collection, rkey string) string { return fmt.Sprintf("at://%s/%s/%s", did, collection, rkey)}
// ParseATURI parses an AT-URI and returns its components.func ParseATURI(uri string) (*URI, error) { atURI, err := syntax.ParseATURI(uri) if err != nil { return nil, fmt.Errorf("invalid AT-URI %q: %w", uri, err) } return &URI{ DID: atURI.Authority().String(), Collection: atURI.Collection().String(), RKey: atURI.RecordKey().String(), }, nil}
// RKeyFromURI extracts the record key from an AT-URI. Returns empty string on error.func RKeyFromURI(uri string) string { u, err := ParseATURI(uri) if err != nil { return "" } return u.RKey}
// MaxRKeyLength is the maximum allowed length for a record key.const MaxRKeyLength = 512
// rkeyRegex validates AT Protocol record keys (rkeys).// Valid rkeys contain only alphanumeric characters, hyphens, underscores, colons, and periods.// They must start with an alphanumeric character and be 1-512 characters long.// TIDs are the most common format: 13 lowercase base32 characters (e.g., "3kfk4slgu6s2h").var rkeyRegex = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,511}$`)
// ValidateRKey checks if an rkey is valid according to AT Protocol spec.// Returns true if valid, false otherwise.func ValidateRKey(rkey string) bool { if rkey == "" || len(rkey) > MaxRKeyLength { return false } // Reserved rkeys that should not be used if rkey == "." || rkey == ".." { return false } return rkeyRegex.MatchString(rkey)}
// NormalizeHandle converts a handle to its ASCII (punycode) form for// resolution and storage. Idempotent on plain ASCII input. Returns the// lowercased input unchanged if IDN conversion fails.func NormalizeHandle(handle string) string { h := strings.TrimPrefix(handle, "@") h = strings.ToLower(strings.TrimSpace(h)) if h == "" { return "" } ascii, err := idna.Lookup.ToASCII(h) if err != nil { return h } return ascii}
// DisplayHandle converts a handle to its Unicode form for display. If the// input is already Unicode (or plain ASCII with no xn-- labels), it is// returned unchanged. Falls back to the input on conversion error.func DisplayHandle(handle string) string { if handle == "" { return "" } unicode, err := idna.Display.ToUnicode(handle) if err != nil { return handle } return unicode}