porting all github actions from bluesky-social/indigo to tangled CI
at ci 2.4 kB view raw
1package syntax 2 3import ( 4 "errors" 5 "regexp" 6 "strings" 7) 8 9// Represents a syntaxtually valid DID identifier, as would pass Lexicon syntax validation. 10// 11// Always use [ParseDID] instead of wrapping strings directly, especially when working with input. 12// 13// Syntax specification: https://atproto.com/specs/did 14type DID string 15 16var didRegex = regexp.MustCompile(`^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$`) 17 18func isASCIIAlphaNum(c rune) bool { 19 if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') { 20 return true 21 } 22 return false 23} 24 25func ParseDID(raw string) (DID, error) { 26 // fast-path for did:plc, avoiding regex 27 if len(raw) == 32 && strings.HasPrefix(raw, "did:plc:") { 28 // NOTE: this doesn't really check base32, just broader alphanumberic. might pass invalid PLC DIDs, but they still have overall valid DID syntax 29 isPlc := true 30 for _, c := range raw[8:32] { 31 if !isASCIIAlphaNum(c) { 32 isPlc = false 33 break 34 } 35 } 36 if isPlc { 37 return DID(raw), nil 38 } 39 } 40 if raw == "" { 41 return "", errors.New("expected DID, got empty string") 42 } 43 if len(raw) > 2*1024 { 44 return "", errors.New("DID is too long (2048 chars max)") 45 } 46 if !didRegex.MatchString(raw) { 47 return "", errors.New("DID syntax didn't validate via regex") 48 } 49 return DID(raw), nil 50} 51 52// The "method" part of the DID, between the 'did:' prefix and the final identifier segment, normalized to lower-case. 53func (d DID) Method() string { 54 // syntax guarantees that there are at least 3 parts of split 55 parts := strings.SplitN(string(d), ":", 3) 56 if len(parts) < 2 { 57 // this should be impossible; return empty to avoid out-of-bounds 58 return "" 59 } 60 return strings.ToLower(parts[1]) 61} 62 63// The final "identifier" segment of the DID 64func (d DID) Identifier() string { 65 // syntax guarantees that there are at least 3 parts of split 66 parts := strings.SplitN(string(d), ":", 3) 67 if len(parts) < 3 { 68 // this should be impossible; return empty to avoid out-of-bounds 69 return "" 70 } 71 return parts[2] 72} 73 74func (d DID) AtIdentifier() AtIdentifier { 75 return AtIdentifier{Inner: d} 76} 77 78func (d DID) String() string { 79 return string(d) 80} 81 82func (d DID) MarshalText() ([]byte, error) { 83 return []byte(d.String()), nil 84} 85 86func (d *DID) UnmarshalText(text []byte) error { 87 did, err := ParseDID(string(text)) 88 if err != nil { 89 return err 90 } 91 *d = did 92 return nil 93}