-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This sets up some support infrastructure to unify naming across claircore. The goal is to be able to pass around names as strings instead of interface objects. Signed-off-by: Hank Donnay <[email protected]>
- Loading branch information
Showing
11 changed files
with
991 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.ri |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package urn | ||
|
||
import ( | ||
"strings" | ||
"testing" | ||
) | ||
|
||
func TestCompliance(t *testing.T) { | ||
t.Run("Valid", func(t *testing.T) { | ||
t.Run("Basic", parseOK(`urn:test:test`)) | ||
t.Run("NID", parseOK(`urn:test-T-0123456789:test`)) | ||
t.Run("NSS", parseOK(`urn:test:Test-0123456789()+,-.:=@;$_!*'`)) | ||
}) | ||
t.Run("Invalid", func(t *testing.T) { | ||
t.Run("NID", func(t *testing.T) { | ||
t.Run("TooLong", parseErr(`urn:`+strings.Repeat("a", 33)+`:test`)) | ||
t.Run("BadChars", parseErr(`urn:test//notOK:test`)) | ||
t.Run("None", parseErr(`urn::test`)) | ||
t.Run("HyphenStart", parseErr(`urn:-nid:test`)) | ||
t.Run("HyphenEnd", parseErr(`urn:nid-:test`)) | ||
}) | ||
t.Run("NSS", func(t *testing.T) { | ||
t.Run("BadChar", parseErr("urn:test:null\x00null")) | ||
}) | ||
}) | ||
t.Run("Equivalence", func(t *testing.T) { | ||
// These test cases are ported out of the RFC. | ||
t.Run("CaseInsensitive", allEqual(`urn:example:a123,z456`, `URN:example:a123,z456`, `urn:EXAMPLE:a123,z456`)) | ||
t.Run("Component", allEqual(`urn:example:a123,z456`, `urn:example:a123,z456?+abc`, `urn:example:a123,z456?=xyz`, `urn:example:a123,z456#789`)) | ||
t.Run("NSS", allNotEqual(`urn:example:a123,z456`, `urn:example:a123,z456/foo`, `urn:example:a123,z456/bar`, `urn:example:a123,z456/baz`)) | ||
t.Run("PercentDecoding", func(t *testing.T) { | ||
p := []string{`urn:example:a123%2Cz456`, `URN:EXAMPLE:a123%2cz456`} | ||
allEqual(p...)(t) | ||
for _, p := range p { | ||
allNotEqual(`urn:example:a123,z456`, p)(t) | ||
} | ||
}) | ||
t.Run("CaseSensitive", allNotEqual(`urn:example:a123,z456`, `urn:example:A123,z456`, `urn:example:a123,Z456`)) | ||
t.Run("PercentEncoding", func(t *testing.T) { | ||
allNotEqual(`urn:example:a123,z456`, `urn:example:%D0%B0123,z456`)(t) | ||
allEqual(`urn:example:а123,z456`, `urn:example:%D0%B0123,z456`)(t) // NB that's \u0430 CYRILLIC SMALL LETTER A | ||
}) | ||
}) | ||
} | ||
|
||
func parseOK(s string) func(*testing.T) { | ||
u, err := Parse(s) | ||
return func(t *testing.T) { | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if _, err := u.R(); err != nil { | ||
t.Error(err) | ||
} | ||
if _, err := u.Q(); err != nil { | ||
t.Error(err) | ||
} | ||
} | ||
} | ||
func parseErr(s string) func(*testing.T) { | ||
u, err := Parse(s) | ||
return func(t *testing.T) { | ||
t.Log(err) | ||
if err != nil { | ||
// OK | ||
return | ||
} | ||
if _, err := u.R(); err == nil { | ||
t.Fail() | ||
} | ||
if _, err := u.Q(); err == nil { | ||
t.Fail() | ||
} | ||
} | ||
} | ||
func allEqual(s ...string) func(*testing.T) { | ||
var err error | ||
u := make([]URN, len(s)) | ||
for i, s := range s { | ||
u[i], err = Parse(s) | ||
if err != nil { | ||
break | ||
} | ||
} | ||
return func(t *testing.T) { | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
for i := range u { | ||
for j := range u { | ||
if !(&u[i]).Equal(&u[j]) { | ||
t.Errorf("%v != %v", &u[i], &u[j]) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
func allNotEqual(s ...string) func(*testing.T) { | ||
var err error | ||
u := make([]URN, len(s)) | ||
for i, s := range s { | ||
u[i], err = Parse(s) | ||
if err != nil { | ||
break | ||
} | ||
} | ||
return func(t *testing.T) { | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
for i := range u { | ||
for j := range u { | ||
if i != j && (&u[i]).Equal(&u[j]) { | ||
t.Errorf("%v == %v", &u[i], &u[j]) | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package urn | ||
|
||
// These functions are adapted out of the net/url package. | ||
// | ||
// URNs have slightly different rules. | ||
|
||
// Copyright 2009 The Go Authors. | ||
|
||
const upperhex = "0123456789ABCDEF" | ||
|
||
// Escape only handles non-ASCII characters and leaves other validation to the | ||
// parsers. | ||
func escape(s string) string { | ||
ct := 0 | ||
for i := 0; i < len(s); i++ { | ||
c := s[i] | ||
if c > 0x7F { | ||
ct++ | ||
} | ||
} | ||
|
||
if ct == 0 { | ||
return s | ||
} | ||
|
||
var buf [64]byte | ||
var t []byte | ||
|
||
required := len(s) + 2*ct | ||
if required <= len(buf) { | ||
t = buf[:required] | ||
} else { | ||
t = make([]byte, required) | ||
} | ||
|
||
j := 0 | ||
for i := 0; i < len(s); i++ { | ||
switch c := s[i]; { | ||
case c > 0x7F: | ||
t[j] = '%' | ||
t[j+1] = upperhex[c>>4] | ||
t[j+2] = upperhex[c&15] | ||
j += 3 | ||
default: | ||
t[j] = s[i] | ||
j++ | ||
} | ||
} | ||
return string(t) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
#!/bin/sh | ||
set -e | ||
|
||
for cmd in ragel-go gofmt sed; do | ||
if ! command -v "$cmd" >/dev/null 2>&1; then | ||
printf 'missing needed command: %s\n' "$cmd" >&2 | ||
exit 99 | ||
fi | ||
done | ||
|
||
ragel-go -s -p -F1 -o _parser.go parser.rl | ||
trap 'rm _parser.go' EXIT | ||
{ | ||
printf '// Code generated by ragel-go DO NOT EDIT.\n\n' | ||
gofmt -s _parser.go | ||
} > parser.go |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package urn | ||
|
||
import ( | ||
"net/url" | ||
"strings" | ||
) | ||
|
||
// Name is a claircore name. | ||
// | ||
// Names are expected to be unique within a claircore system and comparable | ||
// across instances. Names are hierarchical, moving from least specific to most | ||
// specific. | ||
// | ||
// Any pointer fields are optional metadata that may not exist depending on the | ||
// (System, Kind) pair. | ||
type Name struct { | ||
// System scopes to a claircore system or "mode", such as "indexer" or | ||
// "updater". | ||
System string | ||
// Kind scopes to a specific type of object used within the System. | ||
Kind string | ||
// Name scopes to a specific object within the system. | ||
Name string | ||
// Version is the named object's version. | ||
// | ||
// Versions can be ordered with a lexical sort. | ||
Version *string | ||
} | ||
|
||
// String implements fmt.Stringer. | ||
func (n *Name) String() string { | ||
v := url.Values{} | ||
if n.Version != nil { | ||
v.Set("version", *n.Version) | ||
} | ||
u := URN{ | ||
NID: `claircore`, | ||
NSS: strings.Join( | ||
[]string{n.System, n.Kind, n.Name}, | ||
":", | ||
), | ||
q: v.Encode(), | ||
} | ||
|
||
return u.String() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package urn | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
) | ||
|
||
func TestName(t *testing.T) { | ||
version := "1" | ||
tt := []struct { | ||
In string | ||
Want Name | ||
}{ | ||
// Weird cases first: | ||
{ | ||
In: "urn:claircore:indexer:package:test?=version=1&version=999", | ||
Want: Name{ | ||
System: "indexer", | ||
Kind: "package", | ||
Name: "test", | ||
Version: &version, | ||
}, | ||
}, | ||
{ | ||
In: "urn:claircore:indexer:package:test", | ||
Want: Name{ | ||
System: "indexer", | ||
Kind: "package", | ||
Name: "test", | ||
}, | ||
}, | ||
{ | ||
In: "urn:claircore:indexer:package:test?+resolve=something", | ||
Want: Name{ | ||
System: "indexer", | ||
Kind: "package", | ||
Name: "test", | ||
}, | ||
}, | ||
{ | ||
In: "urn:claircore:indexer:package:test#some_anchor", | ||
Want: Name{ | ||
System: "indexer", | ||
Kind: "package", | ||
Name: "test", | ||
}, | ||
}, | ||
|
||
// Some other exhaustive cases: | ||
{ | ||
In: "urn:claircore:indexer:repository:test?=version=1", | ||
Want: Name{ | ||
System: "indexer", | ||
Kind: "repository", | ||
Name: "test", | ||
Version: &version, | ||
}, | ||
}, | ||
{ | ||
In: "urn:claircore:indexer:distribution:test?=version=1", | ||
Want: Name{ | ||
System: "indexer", | ||
Kind: "distribution", | ||
Name: "test", | ||
Version: &version, | ||
}, | ||
}, | ||
{ | ||
In: "urn:claircore:matcher:vulnerability:test?=version=1", | ||
Want: Name{ | ||
System: "matcher", | ||
Kind: "vulnerability", | ||
Name: "test", | ||
Version: &version, | ||
}, | ||
}, | ||
{ | ||
In: "urn:claircore:matcher:enrichment:test?=version=1", | ||
Want: Name{ | ||
System: "matcher", | ||
Kind: "enrichment", | ||
Name: "test", | ||
Version: &version, | ||
}, | ||
}, | ||
} | ||
|
||
for _, tc := range tt { | ||
t.Logf("parse: %q", tc.In) | ||
u, err := Parse(tc.In) | ||
if err != nil { | ||
t.Error(err) | ||
continue | ||
} | ||
got, err := u.Name() | ||
if err != nil { | ||
t.Error(err) | ||
continue | ||
} | ||
want := tc.Want | ||
t.Logf("name: %q", got.String()) | ||
if !cmp.Equal(&got, &want) { | ||
t.Error(cmp.Diff(&got, &want)) | ||
} | ||
} | ||
} |
Oops, something went wrong.