diff --git a/did.go b/did.go index aa087f7..8c08d61 100644 --- a/did.go +++ b/did.go @@ -45,6 +45,51 @@ type parser struct { // a step in the parser state machine that returns the next step type parserStep func() parserStep +// String encodes a DID struct into a valid DID string. +func (d *DID) String() string { + var buf strings.Builder + + // write the did: prefix + buf.WriteString("did:") // nolint, returned error is always nil + + if d.Method != "" { + // write method followed by a `:` + buf.WriteString(d.Method) // nolint, returned error is always nil + buf.WriteByte(':') // nolint, returned error is always nil + } else { + // if there is no Method, return an empty string + return "" + } + + if d.ID != "" { + buf.WriteString(d.ID) // nolint, returned error is always nil + } else if len(d.IDStrings) > 0 { + // join IDStrings with a colon to make the ID + buf.WriteString(strings.Join(d.IDStrings[:], ":")) // nolint, returned error is always nil + } else { + // if there is no ID, return an empty string + return "" + } + + if d.Path != "" { + // write a leading / and then Path + buf.WriteByte('/') // nolint, returned error is always nil + buf.WriteString(d.Path) // nolint, returned error is always nil + } else if len(d.PathSegments) > 0 { + // write a leading / and then PathSegments joined with / between them + buf.WriteByte('/') // nolint, returned error is always nil + buf.WriteString(strings.Join(d.PathSegments[:], "/")) // nolint, returned error is always nil + } else { + // add fragment only when there is no path + if d.Fragment != "" { + buf.WriteByte('#') // nolint, returned error is always nil + buf.WriteString(d.Fragment) // nolint, returned error is always nil + } + } + + return buf.String() +} + // Parse parses the input string into a DID structure. func Parse(input string) (*DID, error) { // intialize the parser state diff --git a/did_test.go b/did_test.go index a8752b2..7734369 100644 --- a/did_test.go +++ b/did_test.go @@ -4,6 +4,91 @@ import ( "testing" ) +// nolint +func TestString(t *testing.T) { + + t.Run("assembles a DID", func(t *testing.T) { + d := &DID{Method: "example", ID: "123"} + output := d.String() + expected := "did:example:123" + if output != expected { + t.Errorf("output: %s, expected: %s", output, expected) + } + }) + + t.Run("assembles a DID from IDStrings", func(t *testing.T) { + d := &DID{Method: "example", IDStrings: []string{"123", "456"}} + output := d.String() + expected := "did:example:123:456" + if output != expected { + t.Errorf("output: %s, expected: %s", output, expected) + } + }) + + t.Run("returns empty string if no method", func(t *testing.T) { + d := &DID{ID: "123"} + output := d.String() + expected := "" + if output != expected { + t.Errorf("output: %s, expected: empty string", output) + } + }) + + t.Run("returns empty string in no ID or IDStrings", func(t *testing.T) { + d := &DID{Method: "example"} + output := d.String() + expected := "" + if output != expected { + t.Errorf("output: %s, expected: empty string", output) + } + }) + + t.Run("includes Path", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Path: "a/b"} + output := d.String() + expected := "did:example:123/a/b" + if output != expected { + t.Errorf("output: %s, expected: %s", output, expected) + } + }) + + t.Run("includes Path assembled from PathSegements", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", PathSegments: []string{"a", "b"}} + output := d.String() + expected := "did:example:123/a/b" + if output != expected { + t.Errorf("output: %s, expected: %s", output, expected) + } + }) + + t.Run("includes Fragment", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Fragment: "00000"} + output := d.String() + expected := "did:example:123#00000" + if output != expected { + t.Errorf("output: %s, expected: %s", output, expected) + } + }) + + t.Run("does not include Fragment if Path is present", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Path: "a/b", Fragment: "00000"} + output := d.String() + expected := "did:example:123/a/b" + if output != expected { + t.Errorf("output: %s, expected: %s", output, expected) + } + }) + + t.Run("does not include Fragment if PathSegments is present", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", PathSegments: []string{"a", "b"}, Fragment: "00000"} + output := d.String() + expected := "did:example:123/a/b" + if output != expected { + t.Errorf("output: %s, expected: %s", output, expected) + } + }) +} + // nolint func TestParse(t *testing.T) { diff --git a/example_test.go b/example_test.go index b707f78..98a3993 100644 --- a/example_test.go +++ b/example_test.go @@ -31,3 +31,27 @@ func ExampleParse_withFragment() { fmt.Printf("%#v", d) // Output: &did.DID{Method:"example", ID:"q7ckgxeq1lxmra0r", IDStrings:[]string{"q7ckgxeq1lxmra0r"}, Path:"", PathSegments:[]string(nil), Fragment:"keys-1"} } + +func ExampleDID_String() { + d := &did.DID{Method: "example", ID: "q7ckgxeq1lxmra0r"} + fmt.Println(d) + // Output: did:example:q7ckgxeq1lxmra0r +} + +func ExampleDID_String_withPath() { + d := &did.DID{Method: "example", ID: "q7ckgxeq1lxmra0r", Path: "a/b"} + fmt.Println(d) + // Output: did:example:q7ckgxeq1lxmra0r/a/b +} + +func ExampleDID_String_withPathSegments() { + d := &did.DID{Method: "example", ID: "q7ckgxeq1lxmra0r", PathSegments: []string{"a", "b"}} + fmt.Println(d) + // Output: did:example:q7ckgxeq1lxmra0r/a/b +} + +func ExampleDID_String_withFragment() { + d := &did.DID{Method: "example", ID: "q7ckgxeq1lxmra0r", Fragment: "keys-1"} + fmt.Println(d) + // Output: did:example:q7ckgxeq1lxmra0r#keys-1 +}