Usability improvements for reference package

Various improvements motivated by early real-world use in engine code
under development:

- Make `WithDigest` return `Canonical`, since the return value always
  has a name and a digest.

- Introduce a `NamedTagged` type, which can be used for strongly typing
  cases where something must have a name and a tag.

- Rename `ParseNamed` to `WithName`, and create a
  `ParseNamed` that accepts tags and digests (returning a `Named` type).
  The new `ParseNamed` makes code using the reference package much less
  verbose, since typical use cases require a name, and this is an
  additional type assertion and error case after every `Parse` call.

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
Aaron Lehmann 2015-10-13 15:42:04 -07:00
parent 4c4112bdcf
commit 16eea0cc47
2 changed files with 31 additions and 9 deletions

View file

@ -114,6 +114,12 @@ type Tagged interface {
Tag() string Tag() string
} }
// NamedTagged is an object including a name and tag.
type NamedTagged interface {
Named
Tag() string
}
// Digested is an object which has a digest // Digested is an object which has a digest
// in which it can be referenced by // in which it can be referenced by
type Digested interface { type Digested interface {
@ -178,10 +184,26 @@ func Parse(s string) (Reference, error) {
return r, nil return r, nil
} }
// ParseNamed parses the input string and returns a named // ParseNamed parses s and returns a syntactically valid reference implementing
// object representing the given string. If the input is // the Named interface. The reference must have a name, otherwise an error is
// invalid ErrReferenceInvalidFormat will be returned. // returned.
func ParseNamed(name string) (Named, error) { // If an error was encountered it is returned, along with a nil Reference.
// NOTE: ParseNamed will not handle short digests.
func ParseNamed(s string) (Named, error) {
ref, err := Parse(s)
if err != nil {
return nil, err
}
named, isNamed := ref.(Named)
if !isNamed {
return nil, fmt.Errorf("reference %s has no name", ref.String())
}
return named, nil
}
// WithName returns a named object representing the given string. If the input
// is invalid ErrReferenceInvalidFormat will be returned.
func WithName(name string) (Named, error) {
if !anchoredNameRegexp.MatchString(name) { if !anchoredNameRegexp.MatchString(name) {
return nil, ErrReferenceInvalidFormat return nil, ErrReferenceInvalidFormat
} }
@ -190,7 +212,7 @@ func ParseNamed(name string) (Named, error) {
// WithTag combines the name from "name" and the tag from "tag" to form a // WithTag combines the name from "name" and the tag from "tag" to form a
// reference incorporating both the name and the tag. // reference incorporating both the name and the tag.
func WithTag(name Named, tag string) (Tagged, error) { func WithTag(name Named, tag string) (NamedTagged, error) {
if !anchoredNameRegexp.MatchString(tag) { if !anchoredNameRegexp.MatchString(tag) {
return nil, ErrTagInvalidFormat return nil, ErrTagInvalidFormat
} }
@ -202,7 +224,7 @@ func WithTag(name Named, tag string) (Tagged, error) {
// WithDigest combines the name from "name" and the digest from "digest" to form // WithDigest combines the name from "name" and the digest from "digest" to form
// a reference incorporating both the name and the digest. // a reference incorporating both the name and the digest.
func WithDigest(name Named, digest digest.Digest) (Digested, error) { func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
if !anchoredDigestRegexp.MatchString(digest.String()) { if !anchoredDigestRegexp.MatchString(digest.String()) {
return nil, ErrDigestInvalidFormat return nil, ErrDigestInvalidFormat
} }

View file

@ -253,7 +253,7 @@ func TestSplitHostname(t *testing.T) {
t.Fail() t.Fail()
} }
named, err := ParseNamed(testcase.input) named, err := WithName(testcase.input)
if err != nil { if err != nil {
failf("error parsing name: %s", err) failf("error parsing name: %s", err)
} }
@ -424,7 +424,7 @@ func TestWithTag(t *testing.T) {
t.Fail() t.Fail()
} }
named, err := ParseNamed(testcase.name) named, err := WithName(testcase.name)
if err != nil { if err != nil {
failf("error parsing name: %s", err) failf("error parsing name: %s", err)
} }
@ -466,7 +466,7 @@ func TestWithDigest(t *testing.T) {
t.Fail() t.Fail()
} }
named, err := ParseNamed(testcase.name) named, err := WithName(testcase.name)
if err != nil { if err != nil {
failf("error parsing name: %s", err) failf("error parsing name: %s", err)
} }