digest.go 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. package model
  2. import (
  3. "fmt"
  4. "log/slog"
  5. "strings"
  6. "unicode"
  7. )
  8. // Digest represents a digest of a model Manifest. It is a comparable value
  9. // type and is immutable.
  10. //
  11. // The zero Digest is not a valid digest.
  12. type Digest struct {
  13. s string
  14. }
  15. // Split returns the digest type and the digest value.
  16. func (d Digest) Split() (typ, digest string) {
  17. typ, digest, _ = strings.Cut(d.s, "-")
  18. return
  19. }
  20. // String returns the digest in the form of "<digest-type>-<digest>", or the
  21. // empty string if the digest is invalid.
  22. func (d Digest) String() string { return d.s }
  23. // IsValid returns true if the digest is valid (not zero).
  24. //
  25. // A valid digest may be created only by ParseDigest, or
  26. // ParseName(name).Digest().
  27. func (d Digest) IsValid() bool { return d.s != "" }
  28. // LogValue implements slog.Value.
  29. func (d Digest) LogValue() slog.Value {
  30. return slog.StringValue(d.String())
  31. }
  32. var (
  33. _ slog.LogValuer = Digest{}
  34. )
  35. // ParseDigest parses a string in the form of "<digest-type>-<digest>" into a
  36. // Digest.
  37. func ParseDigest(s string) Digest {
  38. typ, digest, ok := strings.Cut(s, "-")
  39. if !ok {
  40. typ, digest, ok = strings.Cut(s, ":")
  41. }
  42. if ok && isValidDigestType(typ) && isValidHex(digest) && len(digest) >= 2 {
  43. return Digest{s: fmt.Sprintf("%s-%s", typ, digest)}
  44. }
  45. return Digest{}
  46. }
  47. func MustParseDigest(s string) Digest {
  48. d := ParseDigest(s)
  49. if !d.IsValid() {
  50. panic(fmt.Sprintf("invalid digest: %q", s))
  51. }
  52. return d
  53. }
  54. func isValidDigestType(s string) bool {
  55. if len(s) == 0 {
  56. return false
  57. }
  58. for _, r := range s {
  59. if !unicode.IsLower(r) && !unicode.IsDigit(r) {
  60. return false
  61. }
  62. }
  63. return true
  64. }
  65. func isValidHex(s string) bool {
  66. if len(s) == 0 {
  67. return false
  68. }
  69. for i := range s {
  70. c := s[i]
  71. if c < '0' || c > '9' && c < 'a' || c > 'f' {
  72. return false
  73. }
  74. }
  75. return true
  76. }