Skip to content

Commit

Permalink
break out Resolve methods to separate interface
Browse files Browse the repository at this point in the history
  • Loading branch information
bnewbold committed Feb 18, 2025
1 parent beb6c50 commit 3d46d52
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 43 deletions.
5 changes: 3 additions & 2 deletions atproto/identity/base_directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ type BaseDirectory struct {
}

var _ Directory = (*BaseDirectory)(nil)
var _ Resolver = (*BaseDirectory)(nil)

func (d *BaseDirectory) LookupHandle(ctx context.Context, h syntax.Handle) (*Identity, error) {
h = h.Normalize()
did, err := d.ResolveHandle(ctx, h)
if err != nil {
return nil, err
}
doc, err := d.ResolveDIDDoc(ctx, did)
doc, err := d.ResolveDID(ctx, did)
if err != nil {
return nil, err
}
Expand All @@ -57,7 +58,7 @@ func (d *BaseDirectory) LookupHandle(ctx context.Context, h syntax.Handle) (*Ide
}

func (d *BaseDirectory) LookupDID(ctx context.Context, did syntax.DID) (*Identity, error) {
doc, err := d.ResolveDIDDoc(ctx, did)
doc, err := d.ResolveDID(ctx, did)
if err != nil {
return nil, err
}
Expand Down
5 changes: 0 additions & 5 deletions atproto/identity/cache_directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,6 @@ func (d *CacheDirectory) Lookup(ctx context.Context, a syntax.AtIdentifier) (*Id
return nil, fmt.Errorf("at-identifier neither a Handle nor a DID")
}

func (d *CacheDirectory) ResolveDID(ctx context.Context, did syntax.DID) (map[string]any, error) {
// XXX: cache this kind of doc separately
return d.Inner.ResolveDID(ctx, did)
}

func (d *CacheDirectory) Purge(ctx context.Context, a syntax.AtIdentifier) error {
handle, err := a.AsHandle()
if nil == err { // if not an error, is a handle
Expand Down
35 changes: 21 additions & 14 deletions atproto/identity/did.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,44 @@ import (
"github.com/bluesky-social/indigo/atproto/syntax"
)

// This is a low-level method for resolving a DID to a raw JSON document.
//
// This method does not parse the DID document into an atproto-specific format, and does not bi-directionally verify handles. Most atproto-specific code should use the "Lookup*" methods, which do implement that functionality.
func (d *BaseDirectory) ResolveDID(ctx context.Context, did syntax.DID) (map[string]any, error) {
raw, err := d.resolveDIDBytes(ctx, did)
// Variant of ResolveDID which parses in to a DIDDocument struct.
func (d *BaseDirectory) ResolveDID(ctx context.Context, did syntax.DID) (*DIDDocument, error) {
b, err := d.resolveDIDBytes(ctx, did)
if err != nil {
return nil, err
}

var doc map[string]any
if err := json.Unmarshal(raw, &doc); err != nil {
var doc DIDDocument
if err := json.Unmarshal(b, &doc); err != nil {
return nil, fmt.Errorf("%w: JSON DID document parse: %w", ErrDIDResolutionFailed, err)
}
return doc, nil
if doc.DID != did {
return nil, fmt.Errorf("document ID did not match DID")
}
return &doc, nil
}

// Variant of ResolveDID which parses in to a DIDDocument struct.
func (d *BaseDirectory) ResolveDIDDoc(ctx context.Context, did syntax.DID) (*DIDDocument, error) {
raw, err := d.resolveDIDBytes(ctx, did)
// Low-level method for resolving a DID to a raw JSON document.
//
// This method does not parse the DID document into an atproto-specific format, and does not bi-directionally verify handles. Most atproto-specific code should use the "Lookup*" methods, which do implement that functionality.
func (d *BaseDirectory) ResolveDIDRaw(ctx context.Context, did syntax.DID) (json.RawMessage, error) {
b, err := d.resolveDIDBytes(ctx, did)
if err != nil {
return nil, err
}

// parse as doc, to validate at least some syntax
var doc DIDDocument
if err := json.Unmarshal(raw, &doc); err != nil {
if err := json.Unmarshal(b, &doc); err != nil {
return nil, fmt.Errorf("%w: JSON DID document parse: %w", ErrDIDResolutionFailed, err)
}
return &doc, nil
if doc.DID != did {
return nil, fmt.Errorf("document ID did not match DID")
}

return json.RawMessage(b), nil
}

// Package-internal helper which resolves a DID document to the response bytes.
func (d *BaseDirectory) resolveDIDBytes(ctx context.Context, did syntax.DID) ([]byte, error) {
var b []byte
var err error
Expand Down
11 changes: 3 additions & 8 deletions atproto/identity/directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ import (
"github.com/bluesky-social/indigo/atproto/syntax"
)

// Common for doing atproto identity lookups by DID or handle.
// Ergonomic interface for atproto identity lookup, by DID or handle.
//
// The "Lookup" methods do bi-directional handle verification automatically, and format results in a compact atproto-specific struct ("Identity"). Clients and services should use these methods by default, instead of resolving handles or DIDs separately.
// The "Lookup" methods resolve identities (handle and DID), and return results in a compact, opinionated struct (`Identity`). They do bi-directional handle/DID verification by default. Clients and services should use these methods by default, instead of resolving handles or DIDs separately.
//
// Handles which fail to resolve, or don't match DID alsoKnownAs, are an error. DIDs which resolve but the handle does not resolve back to the DID return an Identity where the Handle is the special `handle.invalid` value.
//
// The "Resolve" methods do direct resolution of just the identifier indicated.
// Looking up a handle which fails to resolve, or don't match DID alsoKnownAs, returns an error. When looking up a DID, if the handle does not resolve back to the DID, the lookup succeeds and returns an `Identity` where the Handle is the special `handle.invalid` value.
//
// Some example implementations of this interface could be:
// - basic direct resolution on every call
Expand All @@ -28,9 +26,6 @@ type Directory interface {
LookupDID(ctx context.Context, did syntax.DID) (*Identity, error)
Lookup(ctx context.Context, atid syntax.AtIdentifier) (*Identity, error)

ResolveDID(ctx context.Context, did syntax.DID) (map[string]any, error)
ResolveHandle(ctx context.Context, handle syntax.Handle) (syntax.DID, error)

// Flushes any cache of the indicated identifier. If directory is not using caching, can ignore this.
Purge(ctx context.Context, i syntax.AtIdentifier) error
}
Expand Down
20 changes: 11 additions & 9 deletions atproto/identity/mock_directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type MockDirectory struct {
}

var _ Directory = (*MockDirectory)(nil)
var _ Resolver = (*MockDirectory)(nil)

func NewMockDirectory() MockDirectory {
return MockDirectory{
Expand Down Expand Up @@ -72,21 +73,22 @@ func (d *MockDirectory) ResolveHandle(ctx context.Context, h syntax.Handle) (syn
return did, nil
}

func (d *MockDirectory) ResolveDID(ctx context.Context, did syntax.DID) (map[string]any, error) {
func (d *MockDirectory) ResolveDID(ctx context.Context, did syntax.DID) (*DIDDocument, error) {
ident, ok := d.Identities[did]
if !ok {
return nil, ErrDIDNotFound
}
doc := ident.DIDDocument()
b, err := json.Marshal(doc)
if err != nil {
return nil, err
}
var m map[string]any
if err := json.Unmarshal(b, &m); err != nil {
return nil, err
return &doc, nil
}

func (d *MockDirectory) ResolveDIDRaw(ctx context.Context, did syntax.DID) (json.RawMessage, error) {
ident, ok := d.Identities[did]
if !ok {
return nil, ErrDIDNotFound
}
return m, nil
doc := ident.DIDDocument()
return json.Marshal(doc)
}

func (d *MockDirectory) Purge(ctx context.Context, a syntax.AtIdentifier) error {
Expand Down
5 changes: 0 additions & 5 deletions atproto/identity/redisdir/redis_directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,3 @@ func (d *RedisDirectory) Purge(ctx context.Context, a syntax.AtIdentifier) error
}
return errors.New("at-identifier neither a Handle nor a DID")
}

func (d *RedisDirectory) ResolveDID(ctx context.Context, did syntax.DID) (map[string]any, error) {
// XXX: cache this kind of doc separately
return d.Inner.ResolveDID(ctx, did)
}
17 changes: 17 additions & 0 deletions atproto/identity/resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package identity

import (
"context"
"encoding/json"

"github.com/bluesky-social/indigo/atproto/syntax"
)

// Low-level interface for resolving DIDs and atproto handles.
//
// Most atproto code should use the `identity.Directory` interface instead.
type Resolver interface {
ResolveDID(ctx context.Context, did syntax.DID) (*DIDDocument, error)
ResolveDIDRaw(ctx context.Context, did syntax.DID) (json.RawMessage, error)
ResolveHandle(ctx context.Context, handle syntax.Handle) (syntax.DID, error)
}

0 comments on commit 3d46d52

Please sign in to comment.