x/model: make Digest just hold a string
This is so much easier to reason about and allows Name to contain only parts, instead of a special field for the digest. It also removes the allocs in Digest.String().
This commit is contained in:
parent
8b62eaf059
commit
06c21f00eb
@ -15,21 +15,17 @@ import (
|
|||||||
//
|
//
|
||||||
// It is comparable with other Digests and can be used as a map key.
|
// It is comparable with other Digests and can be used as a map key.
|
||||||
type Digest struct {
|
type Digest struct {
|
||||||
typ string
|
s string
|
||||||
digest string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Digest) Type() string { return d.typ }
|
func (d Digest) Type() string {
|
||||||
func (d Digest) Digest() string { return d.digest }
|
typ, _, _ := strings.Cut(d.s, "-")
|
||||||
func (d Digest) IsValid() bool { return d != Digest{} }
|
return typ
|
||||||
|
|
||||||
func (d Digest) String() string {
|
|
||||||
if !d.IsValid() {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s-%s", d.typ, d.digest)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d Digest) IsValid() bool { return d.s != "" }
|
||||||
|
func (d Digest) String() string { return d.s }
|
||||||
|
|
||||||
func (d Digest) MarshalText() ([]byte, error) {
|
func (d Digest) MarshalText() ([]byte, error) {
|
||||||
return []byte(d.String()), nil
|
return []byte(d.String()), nil
|
||||||
}
|
}
|
||||||
@ -75,7 +71,7 @@ func (d Digest) Value() (driver.Value, error) {
|
|||||||
func ParseDigest(s string) Digest {
|
func ParseDigest(s string) Digest {
|
||||||
typ, digest, ok := strings.Cut(s, "-")
|
typ, digest, ok := strings.Cut(s, "-")
|
||||||
if ok && isValidDigestType(typ) && isValidHex(digest) {
|
if ok && isValidDigestType(typ) && isValidHex(digest) {
|
||||||
return Digest{typ: typ, digest: digest}
|
return Digest{s: s}
|
||||||
}
|
}
|
||||||
return Digest{}
|
return Digest{}
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,9 @@ import "testing"
|
|||||||
|
|
||||||
var testDigests = map[string]Digest{
|
var testDigests = map[string]Digest{
|
||||||
"": {},
|
"": {},
|
||||||
"sha256-1234": {typ: "sha256", digest: "1234"},
|
"sha256-1234": {s: "sha256-1234"},
|
||||||
"sha256-5678": {typ: "sha256", digest: "5678"},
|
"sha256-5678": {s: "sha256-5678"},
|
||||||
"blake2-9abc": {typ: "blake2", digest: "9abc"},
|
"blake2-9abc": {s: "blake2-9abc"},
|
||||||
"-1234": {},
|
"-1234": {},
|
||||||
"sha256-": {},
|
"sha256-": {},
|
||||||
"sha256-1234-5678": {},
|
"sha256-1234-5678": {},
|
||||||
|
@ -91,9 +91,8 @@ func (k PartKind) String() string {
|
|||||||
//
|
//
|
||||||
// To make a Name by filling in missing parts from another Name, use [Fill].
|
// To make a Name by filling in missing parts from another Name, use [Fill].
|
||||||
type Name struct {
|
type Name struct {
|
||||||
_ structs.Incomparable
|
_ structs.Incomparable
|
||||||
parts [5]string // host, namespace, model, tag, build
|
parts [6]string // host, namespace, model, tag, build
|
||||||
digest Digest // digest is a special part
|
|
||||||
|
|
||||||
// TODO(bmizerany): track offsets and hold s (raw string) here? We
|
// TODO(bmizerany): track offsets and hold s (raw string) here? We
|
||||||
// could pack the offests all into a single uint64 since the first
|
// could pack the offests all into a single uint64 since the first
|
||||||
@ -140,12 +139,8 @@ func ParseName(s string) Name {
|
|||||||
if kind == PartInvalid {
|
if kind == PartInvalid {
|
||||||
return Name{}
|
return Name{}
|
||||||
}
|
}
|
||||||
if kind == PartDigest {
|
if kind == PartDigest && !ParseDigest(part).IsValid() {
|
||||||
r.digest = ParseDigest(part)
|
return Name{}
|
||||||
if !r.digest.IsValid() {
|
|
||||||
return Name{}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
r.parts[kind] = part
|
r.parts[kind] = part
|
||||||
}
|
}
|
||||||
@ -173,7 +168,7 @@ func (r Name) WithBuild(build string) Name {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r Name) WithDigest(digest Digest) Name {
|
func (r Name) WithDigest(digest Digest) Name {
|
||||||
r.digest = digest
|
r.parts[PartDigest] = digest.String()
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,7 +239,8 @@ var seps = [...]string{
|
|||||||
PartNamespace: "/",
|
PartNamespace: "/",
|
||||||
PartModel: ":",
|
PartModel: ":",
|
||||||
PartTag: "+",
|
PartTag: "+",
|
||||||
PartBuild: "",
|
PartBuild: "@",
|
||||||
|
PartDigest: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteTo implements io.WriterTo. It writes the fullest possible display
|
// WriteTo implements io.WriterTo. It writes the fullest possible display
|
||||||
@ -263,16 +259,12 @@ func (r Name) writeTo(w io.StringWriter) {
|
|||||||
if r.parts[i] == "" {
|
if r.parts[i] == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if partsWritten > 0 {
|
if partsWritten > 0 || i == int(PartDigest) {
|
||||||
w.WriteString(seps[i-1])
|
w.WriteString(seps[i-1])
|
||||||
}
|
}
|
||||||
w.WriteString(r.parts[i])
|
w.WriteString(r.parts[i])
|
||||||
partsWritten++
|
partsWritten++
|
||||||
}
|
}
|
||||||
if r.IsResolved() {
|
|
||||||
w.WriteString("@")
|
|
||||||
w.WriteString(r.digest.String())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var builderPool = sync.Pool{
|
var builderPool = sync.Pool{
|
||||||
@ -306,9 +298,6 @@ func (r Name) GoString() string {
|
|||||||
for i := range r.parts {
|
for i := range r.parts {
|
||||||
r.parts[i] = cmp.Or(r.parts[i], "?")
|
r.parts[i] = cmp.Or(r.parts[i], "?")
|
||||||
}
|
}
|
||||||
if !r.IsResolved() {
|
|
||||||
r.digest = Digest{"?", "?"}
|
|
||||||
}
|
|
||||||
return r.String()
|
return r.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,7 +388,7 @@ func (r Name) IsCompleteNoBuild() bool {
|
|||||||
// It is possible to have a valid Name, or a complete Name that is not
|
// It is possible to have a valid Name, or a complete Name that is not
|
||||||
// resolved.
|
// resolved.
|
||||||
func (r Name) IsResolved() bool {
|
func (r Name) IsResolved() bool {
|
||||||
return r.digest.IsValid()
|
return r.Digest().IsValid()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Digest returns the digest part of the Name, if any.
|
// Digest returns the digest part of the Name, if any.
|
||||||
@ -407,7 +396,8 @@ func (r Name) IsResolved() bool {
|
|||||||
// If Digest returns a non-empty string, then [Name.IsResolved] will return
|
// If Digest returns a non-empty string, then [Name.IsResolved] will return
|
||||||
// true, and digest is considered valid.
|
// true, and digest is considered valid.
|
||||||
func (r Name) Digest() Digest {
|
func (r Name) Digest() Digest {
|
||||||
return r.digest
|
// This was already validated by ParseName, so we can just return it.
|
||||||
|
return Digest{r.parts[PartDigest]}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EqualFold reports whether r and o are equivalent model names, ignoring
|
// EqualFold reports whether r and o are equivalent model names, ignoring
|
||||||
|
@ -23,7 +23,7 @@ func fieldsFromName(p Name) fields {
|
|||||||
model: p.parts[PartModel],
|
model: p.parts[PartModel],
|
||||||
tag: p.parts[PartTag],
|
tag: p.parts[PartTag],
|
||||||
build: p.parts[PartBuild],
|
build: p.parts[PartBuild],
|
||||||
digest: p.digest.String(),
|
digest: p.parts[PartDigest],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ var testNames = map[string]fields{
|
|||||||
|
|
||||||
func TestNameParts(t *testing.T) {
|
func TestNameParts(t *testing.T) {
|
||||||
var p Name
|
var p Name
|
||||||
if w, g := int(PartBuild+1), len(p.Parts()); w != g {
|
if w, g := int(PartDigest+1), len(p.Parts()); w != g {
|
||||||
t.Errorf("Parts() = %d; want %d", g, w)
|
t.Errorf("Parts() = %d; want %d", g, w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -235,7 +235,7 @@ func TestNameDisplay(t *testing.T) {
|
|||||||
wantLong: "library/mistral:latest",
|
wantLong: "library/mistral:latest",
|
||||||
wantComplete: "example.com/library/mistral:latest",
|
wantComplete: "example.com/library/mistral:latest",
|
||||||
wantModel: "mistral",
|
wantModel: "mistral",
|
||||||
wantGoString: "example.com/library/mistral:latest+Q4_0@?-?",
|
wantGoString: "example.com/library/mistral:latest+Q4_0@?",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Short Name",
|
name: "Short Name",
|
||||||
@ -244,7 +244,7 @@ func TestNameDisplay(t *testing.T) {
|
|||||||
wantLong: "mistral:latest",
|
wantLong: "mistral:latest",
|
||||||
wantComplete: "mistral:latest",
|
wantComplete: "mistral:latest",
|
||||||
wantModel: "mistral",
|
wantModel: "mistral",
|
||||||
wantGoString: "?/?/mistral:latest+?@?-?",
|
wantGoString: "?/?/mistral:latest+?@?",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Long Name",
|
name: "Long Name",
|
||||||
@ -253,7 +253,7 @@ func TestNameDisplay(t *testing.T) {
|
|||||||
wantLong: "library/mistral:latest",
|
wantLong: "library/mistral:latest",
|
||||||
wantComplete: "library/mistral:latest",
|
wantComplete: "library/mistral:latest",
|
||||||
wantModel: "mistral",
|
wantModel: "mistral",
|
||||||
wantGoString: "?/library/mistral:latest+?@?-?",
|
wantGoString: "?/library/mistral:latest+?@?",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Case Preserved",
|
name: "Case Preserved",
|
||||||
@ -262,7 +262,7 @@ func TestNameDisplay(t *testing.T) {
|
|||||||
wantLong: "Library/Mistral:Latest",
|
wantLong: "Library/Mistral:Latest",
|
||||||
wantComplete: "Library/Mistral:Latest",
|
wantComplete: "Library/Mistral:Latest",
|
||||||
wantModel: "Mistral",
|
wantModel: "Mistral",
|
||||||
wantGoString: "?/Library/Mistral:Latest+?@?-?",
|
wantGoString: "?/Library/Mistral:Latest+?@?",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "With digest",
|
name: "With digest",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user