diff --git a/atproto/syntax/atidentifier.go b/atproto/syntax/atidentifier.go new file mode 100644 index 00000000..0fb7da1c --- /dev/null +++ b/atproto/syntax/atidentifier.go @@ -0,0 +1,61 @@ +package syntax + +import ( + "fmt" + "strings" +) + +type AtIdentifier struct { + Inner interface{} +} + +func ParseAtIdentifier(raw string) (*AtIdentifier, error) { + if strings.HasPrefix(raw, "did:") { + did, err := ParseDID(raw) + if err != nil { + return nil, err + } + return &AtIdentifier{Inner: did}, nil + } + handle, err := ParseHandle(raw) + if err != nil { + return nil, err + } + return &AtIdentifier{Inner: handle}, nil +} + +func (n AtIdentifier) AsHandle() (Handle, error) { + handle, ok := n.Inner.(Handle) + if ok { + return handle, nil + } + return "", fmt.Errorf("AT Identifier is not a Handle") +} + +func (n AtIdentifier) AsDID() (DID, error) { + did, ok := n.Inner.(DID) + if ok { + return did, nil + } + return "", fmt.Errorf("AT Identifier is not a DID") +} + +func (n AtIdentifier) Normalize() AtIdentifier { + handle, ok := n.Inner.(Handle) + if ok { + return AtIdentifier{Inner: handle.Normalize()} + } + return n +} + +func (n AtIdentifier) String() string { + did, ok := n.Inner.(DID) + if ok { + return did.String() + } + handle, ok := n.Inner.(Handle) + if ok { + return handle.String() + } + return "" +} diff --git a/atproto/syntax/atidentifier_test.go b/atproto/syntax/atidentifier_test.go new file mode 100644 index 00000000..7bf1ff4e --- /dev/null +++ b/atproto/syntax/atidentifier_test.go @@ -0,0 +1,50 @@ +package syntax + +import ( + "bufio" + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInteropAtIdentifiersValid(t *testing.T) { + assert := assert.New(t) + file, err := os.Open("testdata/atidentifier_syntax_valid.txt") + assert.NoError(err) + defer file.Close() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if len(line) == 0 || line[0] == '#' { + continue + } + _, err := ParseAtIdentifier(line) + if err != nil { + fmt.Println("FAILED, GOOD: " + line) + } + assert.NoError(err) + } + assert.NoError(scanner.Err()) +} + +func TestInteropAtIdentifiersInvalid(t *testing.T) { + assert := assert.New(t) + file, err := os.Open("testdata/atidentifier_syntax_invalid.txt") + assert.NoError(err) + defer file.Close() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if len(line) == 0 || line[0] == '#' { + continue + } + _, err := ParseAtIdentifier(line) + if err == nil { + fmt.Println("FAILED, BAD: " + line) + } + assert.Error(err) + } + assert.NoError(scanner.Err()) +} diff --git a/atproto/syntax/testdata/atidentifier_syntax_invalid.txt b/atproto/syntax/testdata/atidentifier_syntax_invalid.txt new file mode 100644 index 00000000..f0f84309 --- /dev/null +++ b/atproto/syntax/testdata/atidentifier_syntax_invalid.txt @@ -0,0 +1,28 @@ + +# invalid handles +did:thing.test +did:thing +john-.test +john.0 +john.- +xn--bcher-.tld +john..test +jo_hn.test + +# invalid DIDs +did +didmethodval +method:did:val +did:method: +didmethod:val +did:methodval) +:did:method:val +did:method:val: +did:method:val% +DID:method:val + +# other invalid stuff +email@example.com +@handle@example.com +@handle +blah diff --git a/atproto/syntax/testdata/atidentifier_syntax_valid.txt b/atproto/syntax/testdata/atidentifier_syntax_valid.txt new file mode 100644 index 00000000..cc4a42b0 --- /dev/null +++ b/atproto/syntax/testdata/atidentifier_syntax_valid.txt @@ -0,0 +1,15 @@ + +# allows valid handles +XX.LCS.MIT.EDU +john.test +jan.test +a234567890123456789.test +john2.test +john-john.test + +# allows valid DIDs +did:method:val +did:method:VAL +did:method:val123 +did:method:123 +did:method:val-two