Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate DID URL from components #166

Merged
merged 2 commits into from
Aug 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions dids/did/did.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ type DID struct {
// Spec: https://www.w3.org/TR/did-core/#did-syntax
URI string

// URL represents the DID URI + A network location identifier for a specific resource
// Spec: https://www.w3.org/TR/did-core/#did-url-syntax
URL string

// Method specifies the DID method in the URI, which indicates the underlying
// method-specific identifier scheme (e.g., jwk, dht, key, etc.).
// Spec: https://www.w3.org/TR/did-core/#method-schemes
Expand Down Expand Up @@ -48,8 +44,31 @@ type DID struct {
Fragment string
}

// URL represents the DID URI + A network location identifier for a specific resource
// Spec: https://www.w3.org/TR/did-core/#did-url-syntax
func (d DID) URL() string {
url := d.URI
if len(d.Params) > 0 {
var pairs []string
for key, value := range d.Params {
pairs = append(pairs, fmt.Sprintf("%s=%s", key, value))
}
url += ";" + strings.Join(pairs, ";")
}
if len(d.Path) > 0 {
url += "/" + d.Path
}
if len(d.Query) > 0 {
url += "?" + d.Query
}
if len(d.Fragment) > 0 {
url += "#" + d.Fragment
}
return url
}

func (d DID) String() string {
return d.URL
return d.URL()
}

// MarshalText will convert the given DID's URL into a byte array
Expand Down Expand Up @@ -114,7 +133,6 @@ func Parse(input string) (DID, error) {

did := DID{
URI: "did:" + match[1] + ":" + match[2],
URL: input,
Method: match[1],
ID: match[2],
}
Expand Down
91 changes: 59 additions & 32 deletions dids/did/did_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@ func TestParse(t *testing.T) {
output: map[string]interface{}{
"method": "example",
"id": "123456789abcdefghi",
"uri": "did:example:123456789abcdefghi",
},
},
{
input: "did:example:123456789abcdefghi;foo=bar;baz=qux",
output: map[string]interface{}{
"method": "example",
"id": "123456789abcdefghi",
"alternate": "did:example:123456789abcdefghi;baz=qux;foo=bar",
"method": "example",
"id": "123456789abcdefghi",
"uri": "did:example:123456789abcdefghi",
"params": map[string]string{
"foo": "bar",
"baz": "qux",
Expand All @@ -48,6 +51,7 @@ func TestParse(t *testing.T) {
output: map[string]interface{}{
"method": "example",
"id": "123456789abcdefghi",
"uri": "did:example:123456789abcdefghi",
"query": "foo=bar&baz=qux",
},
},
Expand All @@ -56,6 +60,7 @@ func TestParse(t *testing.T) {
output: map[string]interface{}{
"method": "example",
"id": "123456789abcdefghi",
"uri": "did:example:123456789abcdefghi",
"fragment": "keys-1",
},
},
Expand All @@ -64,62 +69,78 @@ func TestParse(t *testing.T) {
output: map[string]interface{}{
"method": "example",
"id": "123456789abcdefghi",
"uri": "did:example:123456789abcdefghi",
"query": "foo=bar&baz=qux",
"fragment": "keys-1",
},
},
{
input: "did:example:123456789abcdefghi;foo=bar;baz=qux?foo=bar&baz=qux#keys-1",
input: "did:example:123456789abcdefghi;foo=bar;baz=qux?p1=v1&p2=v2#keys-1",
output: map[string]interface{}{
"method": "example",
"id": "123456789abcdefghi",
"params": map[string]string{"foo": "bar", "baz": "qux"},
"query": "foo=bar&baz=qux",
"fragment": "keys-1",
"alternate": "did:example:123456789abcdefghi;baz=quxfoo=bar;?p1=v1&p2=v2#keys-1",
"method": "example",
"id": "123456789abcdefghi",
"uri": "did:example:123456789abcdefghi",
"params": map[string]string{"foo": "bar", "baz": "qux"},
"query": "p1=v1&p2=v2",
"fragment": "keys-1",
},
},
}

for _, v := range vectors {
did, err := did.Parse(v.input)
t.Run(v.input, func(t *testing.T) {
did, err := did.Parse(v.input)

if v.error && err == nil {
t.Errorf("expected error, got nil")
}
if v.error && err == nil {
t.Errorf("expected error, got nil")
}

if err != nil {
if !v.error {
t.Errorf("failed to parse did: %s", err.Error())
if err != nil {
if !v.error {
t.Errorf("failed to parse did: %s", err.Error())
}
return
}
continue
}

assert.Equal[interface{}](t, v.output["method"], did.Method)
assert.Equal[interface{}](t, v.output["id"], did.ID)
// The Params map doesn't have a reliable order, so check both
alt, ok := v.output["alternate"]
if ok {
firstOrder := v.input == did.URL()
secondOrder := alt == did.URL()
assert.True(t, firstOrder || secondOrder, "expected one of the orders to match")
} else {
assert.Equal[interface{}](t, v.input, did.URL())
}
assert.Equal[interface{}](t, v.output["method"], did.Method)
assert.Equal[interface{}](t, v.output["id"], did.ID)
assert.Equal[interface{}](t, v.output["uri"], did.URI)

if v.output["params"] != nil {
params, ok := v.output["params"].(map[string]string)
assert.True(t, ok, "expected params to be map[string]string")
if v.output["params"] != nil {
params, ok := v.output["params"].(map[string]string)
assert.True(t, ok, "expected params to be map[string]string")

for k, v := range params {
assert.Equal[interface{}](t, v, did.Params[k])
for k, v := range params {
assert.Equal[interface{}](t, v, did.Params[k])
}
}
}

if v.output["query"] != nil {
assert.Equal[interface{}](t, v.output["query"], did.Query)
}
if v.output["query"] != nil {
assert.Equal[interface{}](t, v.output["query"], did.Query)
}

if v.output["fragment"] != nil {
assert.Equal[interface{}](t, v.output["fragment"], did.Fragment)
}
if v.output["fragment"] != nil {
assert.Equal[interface{}](t, v.output["fragment"], did.Fragment)
}
})
}
}

func TestDID_ScanValueRoundtrip(t *testing.T) {
tests := []struct {
object did.DID
raw string
alt string
wantErr bool
}{
{
Expand All @@ -128,6 +149,7 @@ func TestDID_ScanValueRoundtrip(t *testing.T) {
},
{
raw: "did:example:123456789abcdefghi;foo=bar;baz=qux",
alt: "did:example:123456789abcdefghi;baz=qux;foo=bar",
object: did.MustParse("did:example:123456789abcdefghi;foo=bar;baz=qux"),
},
{
Expand All @@ -144,6 +166,7 @@ func TestDID_ScanValueRoundtrip(t *testing.T) {
},
{
raw: "did:example:123456789abcdefghi;foo=bar;baz=qux?foo=bar&baz=qux#keys-1",
alt: "did:example:123456789abcdefghi;baz=qux;foo=bar?foo=bar&baz=qux#keys-1",
object: did.MustParse("did:example:123456789abcdefghi;foo=bar;baz=qux?foo=bar&baz=qux#keys-1"),
},
}
Expand All @@ -159,7 +182,11 @@ func TestDID_ScanValueRoundtrip(t *testing.T) {
assert.NoError(t, err)
actual, ok := value.(string)
assert.True(t, ok)
assert.Equal(t, tt.raw, actual)
if tt.alt != "" {
assert.True(t, actual == tt.raw || actual == tt.alt)
} else {
assert.Equal(t, tt.raw, actual)
}
})
}
}
18 changes: 18 additions & 0 deletions dids/diddht/diddht_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/tbd54566975/web5-go/dids/did"
"net/http"
"net/http/httptest"
"testing"
Expand Down Expand Up @@ -160,6 +161,23 @@ func TestDHTResolve(t *testing.T) {
assert.EqualError(t, err, test.expectedErrorMessage)

test.assertResult(t, &result.Document)

parsedDID, err := did.Parse(test.didURI)
assert.NoError(t, err)
assert.Equal(t, test.didURI, parsedDID.URI)

// String -> Parse roundtrip
stringParsedDID, err := did.Parse(parsedDID.String())
assert.NoError(t, err)
assert.Equal(t, parsedDID, stringParsedDID)

// Value -> Scan roundtrip
value, err := parsedDID.Value()
assert.NoError(t, err)
var scannedDID did.DID
err = scannedDID.Scan(value)
assert.NoError(t, err)
assert.Equal(t, parsedDID, scannedDID)
})

}
Expand Down
25 changes: 25 additions & 0 deletions dids/didjwk/didjwk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package didjwk_test

import (
"fmt"
"github.com/tbd54566975/web5-go/dids/did"
"testing"

"github.com/alecthomas/assert/v2"
Expand All @@ -23,6 +24,30 @@ func TestCreate(t *testing.T) {
assert.Equal(t, "did:jwk:"+did.ID, did.URI)
}

func TestParse(t *testing.T) {
source, err := didjwk.Create()
assert.NoError(t, err)
original := source.DID

// URI -> Parse
parseURIDID, err := did.Parse(original.URI)
assert.NoError(t, err)
assert.Equal(t, original, parseURIDID)

// String -> Parse
parseStringDID, err := did.Parse(original.String())
assert.NoError(t, err)
assert.Equal(t, original, parseStringDID)

// Value -> Scan
var scanDID did.DID
value, err := original.Value()
assert.NoError(t, err)
err = scanDID.Scan(value)
assert.NoError(t, err)
assert.Equal(t, original, scanDID)
}

func TestResolveDIDJWK(t *testing.T) {
resolver := &didjwk.Resolver{}
result, err := resolver.Resolve("did:jwk:eyJraWQiOiJ1cm46aWV0ZjpwYXJhbXM6b2F1dGg6andrLXRodW1icHJpbnQ6c2hhLTI1NjpGZk1iek9qTW1RNGVmVDZrdndUSUpqZWxUcWpsMHhqRUlXUTJxb2JzUk1NIiwia3R5IjoiT0tQIiwiY3J2IjoiRWQyNTUxOSIsImFsZyI6IkVkRFNBIiwieCI6IkFOUmpIX3p4Y0tCeHNqUlBVdHpSYnA3RlNWTEtKWFE5QVBYOU1QMWo3azQifQ")
Expand Down
30 changes: 30 additions & 0 deletions dids/didweb/didweb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,36 @@ func TestCreate(t *testing.T) {
document := bearerDID.Document
assert.Equal(t, "did:web:localhost%3A8080", document.ID)
assert.Equal(t, 1, len(document.VerificationMethod))

did := bearerDID.DID
assert.Equal(t, "web", did.Method)
assert.Equal(t, "localhost%3A8080", did.ID)
assert.Equal(t, "did:web:localhost%3A8080", did.URI)
assert.Equal(t, "did:web:localhost%3A8080", did.URL())
}

func TestParse(t *testing.T) {
bearerDID, err := didweb.Create("localhost:8080")
assert.NoError(t, err)
original := bearerDID.DID

// URI -> Parse
parseURIDID, err := did.Parse(original.URI)
assert.NoError(t, err)
assert.Equal(t, original, parseURIDID)

// String -> Parse
parseStringDID, err := did.Parse(original.String())
assert.NoError(t, err)
assert.Equal(t, original, parseStringDID)

// Value -> Scan
var scanDID did.DID
value, err := original.Value()
assert.NoError(t, err)
err = scanDID.Scan(value)
assert.NoError(t, err)
assert.Equal(t, original, scanDID)
}

func TestCreate_WithOptions(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion jws/jws.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ func (jws Decoded) Verify() error {
return fmt.Errorf("failed to resolve DID: %w", err)
}

vmSelector := didcore.ID(did.URL)
vmSelector := didcore.ID(did.URL())
verificationMethod, err := resolutionResult.Document.SelectVerificationMethod(vmSelector)
if err != nil {
return fmt.Errorf("kid does not match any verification method %w", err)
Expand Down
Loading