diff --git a/types/model/name.go b/types/model/name.go index 991fdc82..591c331e 100644 --- a/types/model/name.go +++ b/types/model/name.go @@ -4,7 +4,6 @@ package model import ( "cmp" - "encoding/hex" "errors" "fmt" "log/slog" @@ -171,11 +170,6 @@ func Merge(a, b Name) Name { return a } -// Digest returns the result of [ParseDigest] with the RawDigest field. -func (n Name) Digest() Digest { - return ParseDigest(n.RawDigest) -} - // String returns the name string, in the format that [ParseNameNoDefaults] // accepts as valid, if [Name.IsValid] reports true; otherwise the empty // string is returned. @@ -204,7 +198,7 @@ func (n Name) String() string { // IsValid reports whether all parts of the name are present and valid. The // digest is a special case, and is checked for validity only if present. func (n Name) IsValid() bool { - if n.RawDigest != "" && !ParseDigest(n.RawDigest).IsValid() { + if n.RawDigest != "" && !isValidPart(kindDigest, n.RawDigest) { return false } return n.IsFullyQualified() @@ -282,7 +276,7 @@ func isValidPart(kind partKind, s string) bool { return false } case ':': - if kind != kindHost { + if kind != kindHost && kind != kindDigest { return false } default: @@ -317,75 +311,3 @@ func cutPromised(s, sep string) (before, after string, ok bool) { } return cmp.Or(before, MissingPart), cmp.Or(after, MissingPart), true } - -type DigestType int - -const ( - DigestTypeInvalid DigestType = iota - DigestTypeSHA256 -) - -func (t DigestType) String() string { - if t == DigestTypeSHA256 { - return "sha256" - } - return "unknown" -} - -// Digest represents a type and hash of a digest. It is comparable and can -// be used as a map key. -type Digest struct { - Type DigestType - Hash [32]byte -} - -// ParseDigest parses a digest string into a Digest struct. It accepts both -// the forms: -// -// sha256:deadbeef -// sha256-deadbeef -// -// The hash part must be exactly 64 characters long. -// -// The form "type:hash" does not round trip through [Digest.String]. -func ParseDigest(s string) Digest { - typ, hash, ok := cutLast(s, ":") - if !ok { - typ, hash, ok = cutLast(s, "-") - if !ok { - return Digest{} - } - } - if typ != "sha256" { - return Digest{} - } - var d Digest - n, err := hex.Decode(d.Hash[:], []byte(hash)) - if err != nil || n != 32 { - return Digest{} - } - return Digest{Type: DigestTypeSHA256, Hash: d.Hash} -} - -// IsValid returns true if the digest has a valid Type and Hash. -func (d Digest) IsValid() bool { - if d.Type != DigestTypeSHA256 { - return false - } - return d.Hash != [32]byte{} -} - -// String returns the digest as a string in the form "type-hash". The hash -// is encoded as a hex string. -func (d Digest) String() string { - var b strings.Builder - b.WriteString(d.Type.String()) - b.WriteByte('-') - b.WriteString(hex.EncodeToString(d.Hash[:])) - return b.String() -} - -// LogValue returns a slog.Value that represents the digest as a string. -func (d Digest) LogValue() slog.Value { - return slog.StringValue(d.String()) -} diff --git a/types/model/name_test.go b/types/model/name_test.go index 81de0aca..112df3eb 100644 --- a/types/model/name_test.go +++ b/types/model/name_test.go @@ -2,7 +2,6 @@ package model import ( "reflect" - "strings" "testing" ) @@ -82,10 +81,10 @@ func TestParseNameParts(t *testing.T) { wantValidDigest: false, }, { - in: "model@sha256:" + validSHA256Hex, + in: "model@sha256:123", want: Name{ Model: "model", - RawDigest: "sha256:" + validSHA256Hex, + RawDigest: "sha256:123", }, wantValidDigest: true, }, @@ -97,9 +96,6 @@ func TestParseNameParts(t *testing.T) { if !reflect.DeepEqual(got, tt.want) { t.Errorf("parseName(%q) = %v; want %v", tt.in, got, tt.want) } - if got.Digest().IsValid() != tt.wantValidDigest { - t.Errorf("parseName(%q).Digest().IsValid() = %v; want %v", tt.in, got.Digest().IsValid(), tt.wantValidDigest) - } }) } } @@ -239,57 +235,3 @@ func FuzzName(f *testing.F) { }) } - -const validSHA256Hex = "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" - -func TestParseDigest(t *testing.T) { - cases := map[string]bool{ - "sha256-1000000000000000000000000000000000000000000000000000000000000000": true, - "sha256:1000000000000000000000000000000000000000000000000000000000000000": true, - "sha256:0000000000000000000000000000000000000000000000000000000000000000": false, - - "sha256:" + validSHA256Hex: true, - "sha256-" + validSHA256Hex: true, - - "": false, - "sha134:" + validSHA256Hex: false, - "sha256:" + validSHA256Hex + "x": false, - "sha256:x" + validSHA256Hex: false, - "sha256-" + validSHA256Hex + "x": false, - "sha256-x": false, - } - - for s, want := range cases { - t.Run(s, func(t *testing.T) { - d := ParseDigest(s) - if d.IsValid() != want { - t.Errorf("ParseDigest(%q).IsValid() = %v; want %v", s, d.IsValid(), want) - } - norm := strings.ReplaceAll(s, ":", "-") - if d.IsValid() && d.String() != norm { - t.Errorf("ParseDigest(%q).String() = %q; want %q", s, d.String(), norm) - } - }) - } -} - -func TestDigestString(t *testing.T) { - cases := []struct { - in string - want string - }{ - {in: "sha256:" + validSHA256Hex, want: "sha256-" + validSHA256Hex}, - {in: "sha256-" + validSHA256Hex, want: "sha256-" + validSHA256Hex}, - {in: "", want: "unknown-0000000000000000000000000000000000000000000000000000000000000000"}, - {in: "blah-100000000000000000000000000000000000000000000000000000000000000", want: "unknown-0000000000000000000000000000000000000000000000000000000000000000"}, - } - - for _, tt := range cases { - t.Run(tt.in, func(t *testing.T) { - d := ParseDigest(tt.in) - if d.String() != tt.want { - t.Errorf("ParseDigest(%q).String() = %q; want %q", tt.in, d.String(), tt.want) - } - }) - } -}