digest.go 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. package model
  2. import (
  3. "database/sql"
  4. "database/sql/driver"
  5. "errors"
  6. "fmt"
  7. "log/slog"
  8. "strings"
  9. "unicode"
  10. )
  11. // Digest represents a digest of a model Manifest. It is a comparable value
  12. // type and is immutable.
  13. //
  14. // The zero Digest is not a valid digest.
  15. type Digest struct {
  16. s string
  17. }
  18. // Type returns the digest type of the digest.
  19. //
  20. // Example:
  21. //
  22. // ParseDigest("sha256-1234").Type() // returns "sha256"
  23. func (d Digest) Type() string {
  24. typ, _, _ := strings.Cut(d.s, "-")
  25. return typ
  26. }
  27. // String returns the digest in the form of "<digest-type>-<digest>", or the
  28. // empty string if the digest is invalid.
  29. func (d Digest) String() string { return d.s }
  30. // IsValid returns true if the digest is valid (not zero).
  31. //
  32. // A valid digest may be created only by ParseDigest, or
  33. // ParseName(name).Digest().
  34. func (d Digest) IsValid() bool { return d.s != "" }
  35. // LogValue implements slog.Value.
  36. func (d Digest) LogValue() slog.Value {
  37. return slog.StringValue(d.String())
  38. }
  39. var (
  40. _ driver.Valuer = Digest{}
  41. _ sql.Scanner = (*Digest)(nil)
  42. _ slog.LogValuer = Digest{}
  43. )
  44. // Scan implements the sql.Scanner interface.
  45. func (d *Digest) Scan(src any) error {
  46. if d.IsValid() {
  47. return errors.New("model.Digest: illegal Scan on valid Digest")
  48. }
  49. switch v := src.(type) {
  50. case string:
  51. *d = ParseDigest(v)
  52. return nil
  53. case []byte:
  54. *d = ParseDigest(string(v))
  55. return nil
  56. }
  57. return fmt.Errorf("model.Digest: invalid Scan source %T", src)
  58. }
  59. // Value implements the driver.Valuer interface.
  60. func (d Digest) Value() (driver.Value, error) {
  61. return d.String(), nil
  62. }
  63. // ParseDigest parses a string in the form of "<digest-type>-<digest>" into a
  64. // Digest.
  65. func ParseDigest(s string) Digest {
  66. typ, digest, ok := strings.Cut(s, "-")
  67. if ok && isValidDigestType(typ) && isValidHex(digest) {
  68. return Digest{s: s}
  69. }
  70. return Digest{}
  71. }
  72. func isValidDigestType(s string) bool {
  73. if len(s) == 0 {
  74. return false
  75. }
  76. for _, r := range s {
  77. if !unicode.IsLower(r) && !unicode.IsDigit(r) {
  78. return false
  79. }
  80. }
  81. return true
  82. }
  83. func isValidHex(s string) bool {
  84. if len(s) == 0 {
  85. return false
  86. }
  87. for i := range s {
  88. c := s[i]
  89. if c < '0' || c > '9' && c < 'a' || c > 'f' {
  90. return false
  91. }
  92. }
  93. return true
  94. }