From e604f9bd12e376e4ee5f1daf9a2bcae9f1213f8d Mon Sep 17 00:00:00 2001 From: Pat Honeycutt Date: Mon, 3 Feb 2020 20:59:02 -0700 Subject: [PATCH] feat: Updates to DID v1.0 1. modifies the DID struct and parser to handle the param rule addition 2. removes references to did-reference, and replaces them with did-url 3. removes a constraint on fragment existence in the parser String method requiring the existence of a path or path segments 4. adds test and example coverage For #8 --- did.abnf | 37 ++++--- did.go | 232 ++++++++++++++++++++++++++++++++++++++---- did_test.go | 265 +++++++++++++++++++++++++++++++++++++++++++++--- example_test.go | 12 +-- 4 files changed, 492 insertions(+), 54 deletions(-) diff --git a/did.abnf b/did.abnf index 52567ec..504582c 100644 --- a/did.abnf +++ b/did.abnf @@ -5,27 +5,32 @@ ; URI Spec: https://tools.ietf.org/html/rfc3986 ; ABNF Spec: https://tools.ietf.org/html/rfc5234 -did-reference = did [ "/" did-path ] [ "?" did-query ] [ "#" did-fragment ] - -did = "did:" method ":" specific-idstring - -method = 1*methodchar -methodchar = %x61-7A / DIGIT ; 61-7A is a-z in US-ASCII -specific-idstring = idstring *( ":" idstring ) -idstring = 1*idchar -idchar = ALPHA / DIGIT / "." / "-" - -; did-path is identical to a URI path and MUST conform to the ABNF of the path-rootless ABNF rule in [RFC3986]. +did = "did:" method-name ":" method-specific-id +method-name = 1*method-char +method-char = %x61-7A / DIGIT +method-specific-id = *idchar *( ":" *idchar ) +idchar = ALPHA / DIGIT / "." / "-" / "_" +did-url = did *( ";" param ) path-abempty [ "?" query ] + [ "#" fragment ] +param = param-name [ "=" param-value ] +param-name = 1*param-char +param-value = *param-char +param-char = ALPHA / DIGIT / "." / "-" / "_" / ":" / + pct-encoded + + + +; A generic DID path is identical to a URI path and MUST conform to the path-abempty ABNF rule in [RFC3986] ; https://tools.ietf.org/html/rfc3986#section-3.3 -did-path = segment-nz *( "/" segment ) +path-abempty = *( "/" segment ) -; did-fragment is identical to a URI fragment and MUST conform to the ABNF of the fragment ABNF rule in [RFC3986] +; A generic DID fragment is identical to a URI fragment and MUST conform to the fragment ABNF rule in [RFC3986] ; https://tools.ietf.org/html/rfc3986#section-3.5 -did-fragment = *( pchar / "/" / "?" ) +fragment = *( pchar / "/" / "?" ) -; did-query is identical to a URI query and MUST conform to the ABNF of the query ABNF rule in [RFC3986] +; A generic DID query is identical to a URI query and MUST conform to the query ABNF rule in [RFC3986] ; https://tools.ietf.org/html/rfc3986#section-3.4 -did-query = *( pchar / "/" / "?" ) +query = *( pchar / "/" / "?" ) segment = *pchar segment-nz = 1*pchar diff --git a/did.go b/did.go index b87070d..f8bf258 100644 --- a/did.go +++ b/did.go @@ -1,5 +1,5 @@ // Package did is a set of tools to work with Decentralized Identifiers (DIDs) as described -// in the DID spec https://w3c-ccg.github.io/did-spec +// in the DID spec https://w3c.github.io/did-core/ package did import ( @@ -7,35 +7,71 @@ import ( "strings" ) -// A DID represents a parsed DID or a DID Reference +// Param represents a parsed DID param, +// which contains a name and value. A generic param is defined +// as a param name and value separated by a colon. +// generic-param-name:param-value +// A param may also be method specific, which +// requires the method name to prefix the param name separated by a colon +// method-name:param-name. +// param = param-name [ "=" param-value ] +// https://w3c.github.io/did-core/#generic-did-parameter-names +// https://w3c.github.io/did-core/#method-specific-did-parameter-names +type Param struct { + // param-name = 1*param-char + // Name may include a method name and param name separated by a colon + Name string + // param-value = *param-char + Value string +} + +// String encodes a Param struct into a valid Param string. +// Name is required by the grammar. Value is optional +func (p *Param) String() string { + if p.Name == "" { + return "" + } + + if 0 < len(p.Value) { + return p.Name + "=" + p.Value + } + + return p.Name +} + +// A DID represents a parsed DID or a DID URL type DID struct { // DID Method - // https://w3c-ccg.github.io/did-spec#dfn-did-method + // https://w3c.github.io/did-core/#method-specific-syntax Method string - // The specific-idstring component of a DID + // The method-specific-id component of a DID + // method-specific-id = *idchar *( ":" *idchar ) ID string - // specific-idstring may be composed of multiple `:` separated idstrings - // did = "did:" method ":" specific-idstring - // specific-idstring = idstring *( ":" idstring ) + // method-specific-id may be composed of multiple `:` separated idstrings IDStrings []string + // DID URL + // did-url = did *( ";" param ) path-abempty [ "?" query ] [ "#" fragment ] + // did-url may contain multiple params, a path, query, and fragment + Params []Param + // DID Path, the portion of a DID reference that follows the first forward slash character. - // https://w3c-ccg.github.io/did-spec/#dfn-did-path + // https://w3c.github.io/did-core/#path Path string // Path may be composed of multiple `/` separated segments - // did-path = segment-nz *( "/" segment ) + // path-abempty = *( "/" segment ) PathSegments []string // DID Query - // https://github.com/w3c-ccg/did-spec/issues/85 - // did-query = *( pchar / "/" / "?" ) + // https://w3c.github.io/did-core/#query + // query = *( pchar / "/" / "?" ) Query string // DID Fragment, the portion of a DID reference that follows the first hash sign character ("#") - // https://w3c-ccg.github.io/did-spec/#dfn-did-fragment + // https://w3c.github.io/did-core/#fragment Fragment string } @@ -50,13 +86,14 @@ type parser struct { // a step in the parser state machine that returns the next step type parserStep func() parserStep -// IsReference returns true if a DID has a Path, a Query or a Fragment +// IsURL returns true if a DID has a Path, a Query or a Fragment // https://w3c-ccg.github.io/did-spec/#dfn-did-reference -func (d *DID) IsReference() bool { - return (d.Path != "" || len(d.PathSegments) > 0 || d.Query != "" || d.Fragment != "") +func (d *DID) IsURL() bool { + return (len(d.Params) > 0 || d.Path != "" || len(d.PathSegments) > 0 || d.Query != "" || d.Fragment != "") } // String encodes a DID struct into a valid DID string. +// nolint: gocyclo func (d *DID) String() string { var buf strings.Builder @@ -82,6 +119,22 @@ func (d *DID) String() string { return "" } + if len(d.Params) > 0 { + // write a leading ; for each param + for _, p := range d.Params { + // get a string that represents the param + param := p.String() + if param != "" { + // params must start with a ; + buf.WriteByte(';') // nolint, returned error is always nil + buf.WriteString(param) // nolint, returned error is always nil + } else { + // if a param exists but is empty, return an empty string + return "" + } + } + } + if d.Path != "" { // write a leading / and then Path buf.WriteByte('/') // nolint, returned error is always nil @@ -98,7 +151,7 @@ func (d *DID) String() string { buf.WriteString(d.Query) // nolint, returned error is always nil } - if d.Fragment != "" && d.Path == "" && len(d.PathSegments) == 0 { + if d.Fragment != "" { // add fragment only when there is no path buf.WriteByte('#') // nolint, returned error is always nil buf.WriteString(d.Fragment) // nolint, returned error is always nil @@ -251,6 +304,12 @@ func (p *parser) parseID() parserStep { break } + if char == ';' { + // encountered ; input may have a parameter, parse that next + next = p.parseParamName + break + } + if char == '/' { // encountered / input may have a path following specific-idstring, parse that next next = p.parsePath @@ -295,6 +354,137 @@ func (p *parser) parseID() parserStep { return next } +// parseParamName is a parserStep that extracts a did-url param-name. +// A Param struct is created for each param name that is encountered. +// from the grammar: +// param = param-name [ "=" param-value ] +// param-name = 1*param-char +// param-char = ALPHA / DIGIT / "." / "-" / "_" / ":" / pct-encoded +func (p *parser) parseParamName() parserStep { + input := p.input + startIndex := p.currentIndex + 1 + next := p.paramTransition() + currentIndex := p.currentIndex + + if currentIndex == startIndex { + // param-name length is zero + // from the grammar: + // 1*param-char + // return error because param-name is empty, ex- did:a::123:456;param-name + return p.errorf(currentIndex, "Param name must be at least one char long") + } + + // Create a new param with the name + p.out.Params = append(p.out.Params, Param{Name: input[startIndex:currentIndex], Value: ""}) + + // return the next parser step + return next +} + +// parseParamValue is a parserStep that extracts a did-url param-value. +// A parsed Param value requires that a Param was previously created when parsing a param-name. +// from the grammar: +// param = param-name [ "=" param-value ] +// param-value = 1*param-char +// param-char = ALPHA / DIGIT / "." / "-" / "_" / ":" / pct-encoded +func (p *parser) parseParamValue() parserStep { + input := p.input + startIndex := p.currentIndex + 1 + next := p.paramTransition() + currentIndex := p.currentIndex + + // Get the last Param in the DID and append the value + // values may be empty according to the grammar- *param-char + p.out.Params[len(p.out.Params)-1].Value = input[startIndex:currentIndex] + + // return the next parser step + return next +} + +// paramTransition is a parserStep that extracts and transitions a param-name or +// param-value. +// nolint: gocyclo +func (p *parser) paramTransition() parserStep { + input := p.input + inputLength := len(input) + currentIndex := p.currentIndex + 1 + + var indexIncrement int + var next parserStep + var percentEncoded bool + + for { + if currentIndex == inputLength { + // we've reached end of input, no next state + next = nil + break + } + + char := input[currentIndex] + + if char == ';' { + // encountered : input may have another param, parse paramName again + next = p.parseParamName + break + } + + // Separate steps for name and value? + if char == '=' { + // parse param value + next = p.parseParamValue + break + } + + if char == '/' { + // encountered / input may have a path following current param, parse that next + next = p.parsePath + break + } + + if char == '?' { + // encountered ? input may have a query following current param, parse that next + next = p.parseQuery + break + } + + if char == '#' { + // encountered # input may have a fragment following current param, parse that next + next = p.parseFragment + break + } + + if char == '%' { + // a % must be followed by 2 hex digits + if (currentIndex+2 >= inputLength) || + isNotHexDigit(input[currentIndex+1]) || + isNotHexDigit(input[currentIndex+2]) { + return p.errorf(currentIndex, "%% is not followed by 2 hex digits") + } + // if we got here, we're dealing with percent encoded char, jump three chars + percentEncoded = true + indexIncrement = 3 + } else { + // not percent encoded + percentEncoded = false + indexIncrement = 1 + } + + // make sure current char is a valid param-char + // idchar = ALPHA / DIGIT / "." / "-" + if !percentEncoded && isNotValidParamChar(char) { + return p.errorf(currentIndex, "character is not allowed in param - %c", char) + } + + // move to the next char + currentIndex = currentIndex + indexIncrement + } + + // set parser state + p.currentIndex = currentIndex + + return next +} + // parsePath is a parserStep that extracts a DID Path from a DID Reference // from the grammar: // did-path = segment-nz *( "/" segment ) @@ -518,12 +708,20 @@ func (p *parser) errorf(index int, format string, args ...interface{}) parserSte // See output of `go build -gcflags -m` to confirm // isNotValidIDChar returns true if a byte is not allowed in a ID -// from the greammar: +// from the grammar: // idchar = ALPHA / DIGIT / "." / "-" func isNotValidIDChar(char byte) bool { return isNotAlpha(char) && isNotDigit(char) && char != '.' && char != '-' } +// isNotValidParamChar returns true if a byte is not allowed in a param-name +// or param-value from the grammar: +// idchar = ALPHA / DIGIT / "." / "-" / "_" / ":" +func isNotValidParamChar(char byte) bool { + return isNotAlpha(char) && isNotDigit(char) && + char != '.' && char != '-' && char != '_' && char != ':' +} + // isNotValidQueryOrFragmentChar returns true if a byte is not allowed in a Fragment // from the grammar: // did-fragment = *( pchar / "/" / "?" ) diff --git a/did_test.go b/did_test.go index 21ecbad..1b286d8 100644 --- a/did_test.go +++ b/did_test.go @@ -8,35 +8,40 @@ import ( "testing" ) -func TestIsReference(t *testing.T) { +func TestIsURL(t *testing.T) { t.Run("returns false if no Path or Fragment", func(t *testing.T) { d := &DID{Method: "example", ID: "123"} - assert(t, false, d.IsReference()) + assert(t, false, d.IsURL()) + }) + + t.Run("returns true if Params", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "foo", Value: "bar"}}} + assert(t, true, d.IsURL()) }) t.Run("returns true if Path", func(t *testing.T) { d := &DID{Method: "example", ID: "123", Path: "a/b"} - assert(t, true, d.IsReference()) + assert(t, true, d.IsURL()) }) t.Run("returns true if PathSegements", func(t *testing.T) { d := &DID{Method: "example", ID: "123", PathSegments: []string{"a", "b"}} - assert(t, true, d.IsReference()) + assert(t, true, d.IsURL()) }) t.Run("returns true if Query", func(t *testing.T) { d := &DID{Method: "example", ID: "123", Query: "abc"} - assert(t, true, d.IsReference()) + assert(t, true, d.IsURL()) }) t.Run("returns true if Fragment", func(t *testing.T) { d := &DID{Method: "example", ID: "123", Fragment: "00000"} - assert(t, true, d.IsReference()) + assert(t, true, d.IsURL()) }) t.Run("returns true if Path and Fragment", func(t *testing.T) { d := &DID{Method: "example", ID: "123", Path: "a/b", Fragment: "00000"} - assert(t, true, d.IsReference()) + assert(t, true, d.IsURL()) }) } @@ -61,6 +66,37 @@ func TestString(t *testing.T) { assert(t, "", d.String()) }) + t.Run("returns empty string if Param Name does not exist", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "", Value: "agent"}}} + assert(t, "", d.String()) + }) + + t.Run("returns name string if Param Value does not exist", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "service", Value: ""}}} + assert(t, "did:example:123;service", d.String()) + }) + + t.Run("returns param string with name and value", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "service", Value: "agent"}}} + assert(t, "did:example:123;service=agent", d.String()) + }) + + t.Run("includes Param generic", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "service", Value: "agent"}}} + assert(t, "did:example:123;service=agent", d.String()) + }) + + t.Run("includes Param method", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Params: []Param{{Name: "foo:bar", Value: "high"}}} + assert(t, "did:example:123;foo:bar=high", d.String()) + }) + + t.Run("includes Param generic and method", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", + Params: []Param{{Name: "service", Value: "agent"}, {Name: "foo:bar", Value: "high"}}} + assert(t, "did:example:123;service=agent;foo:bar=high", d.String()) + }) + t.Run("includes Path", func(t *testing.T) { d := &DID{Method: "example", ID: "123", Path: "a/b"} assert(t, "did:example:123/a/b", d.String()) @@ -71,16 +107,34 @@ func TestString(t *testing.T) { assert(t, "did:example:123/a/b", d.String()) }) + t.Run("includes Path after Param", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", + Params: []Param{{Name: "service", Value: "agent"}}, Path: "a/b"} + assert(t, "did:example:123;service=agent/a/b", d.String()) + }) + t.Run("includes Query after IDString", func(t *testing.T) { d := &DID{Method: "example", ID: "123", Query: "abc"} assert(t, "did:example:123?abc", d.String()) }) + t.Run("include Query after Param", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Query: "abc", + Params: []Param{{Name: "service", Value: "agent"}}} + assert(t, "did:example:123;service=agent?abc", d.String()) + }) + t.Run("includes Query after Path", func(t *testing.T) { d := &DID{Method: "example", ID: "123", Path: "x/y", Query: "abc"} assert(t, "did:example:123/x/y?abc", d.String()) }) + t.Run("includes Query after Param and Path", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Path: "x/y", Query: "abc", + Params: []Param{{Name: "service", Value: "agent"}}} + assert(t, "did:example:123;service=agent/x/y?abc", d.String()) + }) + t.Run("includes Query after before Fragment", func(t *testing.T) { d := &DID{Method: "example", ID: "123", Fragment: "zyx", Query: "abc"} assert(t, "did:example:123?abc#zyx", d.String()) @@ -96,14 +150,9 @@ func TestString(t *testing.T) { assert(t, "did:example:123#00000", d.String()) }) - 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"} - assert(t, "did:example:123/a/b", d.String()) - }) - - 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"} - assert(t, "did:example:123/a/b", d.String()) + t.Run("includes Fragment after Param", func(t *testing.T) { + d := &DID{Method: "example", ID: "123", Fragment: "00000"} + assert(t, "did:example:123#00000", d.String()) }) } @@ -203,6 +252,175 @@ func TestParse(t *testing.T) { assert(t, false, err == nil) }) + t.Run("returns error if param name is empty", func(t *testing.T) { + _, err := Parse("did:a:123:456;") + assert(t, false, err == nil) + }) + + t.Run("returns error if Param name has an invalid char", func(t *testing.T) { + _, err := Parse("did:a:123:456;serv&ce") + assert(t, false, err == nil) + }) + + t.Run("returns error if Param value has an invalid char", func(t *testing.T) { + _, err := Parse("did:a:123:456;service=ag&nt") + assert(t, false, err == nil) + }) + + t.Run("returns error if Param name has an invalid percent encoded", func(t *testing.T) { + _, err := Parse("did:a:123:456;ser%2ge") + assert(t, false, err == nil) + }) + + t.Run("returns error if Param does not exist for value", func(t *testing.T) { + _, err := Parse("did:a:123:456;=value") + assert(t, false, err == nil) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract generic param with name and value", func(t *testing.T) { + d, err := Parse("did:a:123:456;service==agent") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service=agent", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "agent", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract generic param with name only", func(t *testing.T) { + d, err := Parse("did:a:123:456;service") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract generic param with name only and empty param", func(t *testing.T) { + d, err := Parse("did:a:123:456;service=") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract method param with name and value", func(t *testing.T) { + d, err := Parse("did:a:123:456;foo:bar=baz") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "foo:bar=baz", d.Params[0].String()) + assert(t, "foo:bar", d.Params[0].Name) + assert(t, "baz", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract method param with name only", func(t *testing.T) { + d, err := Parse("did:a:123:456;foo:bar") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "foo:bar", d.Params[0].String()) + assert(t, "foo:bar", d.Params[0].Name) + assert(t, "", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds with percent encoded chars in param name and value", func(t *testing.T) { + d, err := Parse("did:a:123:456;serv%20ice=val%20ue") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "serv%20ice=val%20ue", d.Params[0].String()) + assert(t, "serv%20ice", d.Params[0].Name) + assert(t, "val%20ue", d.Params[0].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract multiple generic params with name only", func(t *testing.T) { + d, err := Parse("did:a:123:456;foo;bar") + assert(t, nil, err) + assert(t, 2, len(d.Params)) + assert(t, "foo", d.Params[0].Name) + assert(t, "", d.Params[0].Value) + assert(t, "bar", d.Params[1].Name) + assert(t, "", d.Params[1].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract multiple params with names and values", func(t *testing.T) { + d, err := Parse("did:a:123:456;service=agent;foo:bar=baz") + assert(t, nil, err) + assert(t, 2, len(d.Params)) + assert(t, "service", d.Params[0].Name) + assert(t, "agent", d.Params[0].Value) + assert(t, "foo:bar", d.Params[1].Name) + assert(t, "baz", d.Params[1].Value) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract path after generic param", func(t *testing.T) { + d, err := Parse("did:a:123:456;service==value/a/b") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service=value", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "value", d.Params[0].Value) + + segments := d.PathSegments + assert(t, "a", segments[0]) + assert(t, "b", segments[1]) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract path after generic param name and no value", func(t *testing.T) { + d, err := Parse("did:a:123:456;service=/a/b") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "", d.Params[0].Value) + + segments := d.PathSegments + assert(t, "a", segments[0]) + assert(t, "b", segments[1]) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract query after generic param", func(t *testing.T) { + d, err := Parse("did:a:123:456;service=value?abc") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service=value", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "value", d.Params[0].Value) + assert(t, "abc", d.Query) + }) + + // nolint: dupl + // test for params look similar to linter + t.Run("succeeds to extract fragment after generic param", func(t *testing.T) { + d, err := Parse("did:a:123:456;service=value#xyz") + assert(t, nil, err) + assert(t, 1, len(d.Params)) + assert(t, "service=value", d.Params[0].String()) + assert(t, "service", d.Params[0].Name) + assert(t, "value", d.Params[0].Value) + assert(t, "xyz", d.Fragment) + }) + t.Run("succeeds to extract path", func(t *testing.T) { d, err := Parse("did:a:123:456/someService") assert(t, nil, err) @@ -361,6 +579,23 @@ func Test_errorf(t *testing.T) { } } +func Test_isNotValidParamChar(t *testing.T) { + a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '.', '-', '_', ':'} + for _, c := range a { + assert(t, false, isNotValidParamChar(c), "Input: '%c'", c) + } + + a = []byte{'%', '^', '#', ' ', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', '@', '/', '?'} + for _, c := range a { + assert(t, true, isNotValidParamChar(c), "Input: '%c'", c) + } +} + func Test_isNotValidIDChar(t *testing.T) { a := []byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', diff --git a/example_test.go b/example_test.go index 783fc0f..204d5a5 100644 --- a/example_test.go +++ b/example_test.go @@ -73,20 +73,20 @@ func ExampleDID_String_withFragment() { // Output: did:example:q7ckgxeq1lxmra0r#keys-1 } -func ExampleDID_IsReference_withPath() { +func ExampleDID_IsURL_withPath() { d := &did.DID{Method: "example", ID: "q7ckgxeq1lxmra0r", Path: "a/b"} - fmt.Println(d.IsReference()) + fmt.Println(d.IsURL()) // Output: true } -func ExampleDID_IsReference_withFragment() { +func ExampleDID_IsURL_withFragment() { d := &did.DID{Method: "example", ID: "q7ckgxeq1lxmra0r", Fragment: "keys-1"} - fmt.Println(d.IsReference()) + fmt.Println(d.IsURL()) // Output: true } -func ExampleDID_IsReference_noPathOrFragment() { +func ExampleDID_IsURL_noPathOrFragment() { d := &did.DID{Method: "example", ID: "q7ckgxeq1lxmra0r"} - fmt.Println(d.IsReference()) + fmt.Println(d.IsURL()) // Output: false }