digest.go 1.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  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. // Type returns the digest type of the digest.
  16. //
  17. // Example:
  18. //
  19. // ParseDigest("sha256-1234").Type() // returns "sha256"
  20. func (d Digest) Type() string {
  21. typ, _, _ := strings.Cut(d.s, "-")
  22. return typ
  23. }
  24. // String returns the digest in the form of "<digest-type>-<digest>", or the
  25. // empty string if the digest is invalid.
  26. func (d Digest) String() string { return d.s }
  27. // IsValid returns true if the digest is valid (not zero).
  28. //
  29. // A valid digest may be created only by ParseDigest, or
  30. // ParseName(name).Digest().
  31. func (d Digest) IsValid() bool { return d.s != "" }
  32. // LogValue implements slog.Value.
  33. func (d Digest) LogValue() slog.Value {
  34. return slog.StringValue(d.String())
  35. }
  36. var (
  37. _ slog.LogValuer = Digest{}
  38. )
  39. // ParseDigest parses a string in the form of "<digest-type>-<digest>" into a
  40. // Digest.
  41. func ParseDigest(s string) Digest {
  42. typ, digest, ok := strings.Cut(s, "-")
  43. if !ok {
  44. typ, digest, ok = strings.Cut(s, ":")
  45. }
  46. if ok && isValidDigestType(typ) && isValidHex(digest) {
  47. return Digest{s: fmt.Sprintf("%s-%s", typ, digest)}
  48. }
  49. return Digest{}
  50. }
  51. func isValidDigestType(s string) bool {
  52. if len(s) == 0 {
  53. return false
  54. }
  55. for _, r := range s {
  56. if !unicode.IsLower(r) && !unicode.IsDigit(r) {
  57. return false
  58. }
  59. }
  60. return true
  61. }
  62. func isValidHex(s string) bool {
  63. if len(s) == 0 {
  64. return false
  65. }
  66. for i := range s {
  67. c := s[i]
  68. if c < '0' || c > '9' && c < 'a' || c > 'f' {
  69. return false
  70. }
  71. }
  72. return true
  73. }