From 1bbf75ae193c50f095af6de3adb78b0847f0f023 Mon Sep 17 00:00:00 2001 From: mesh-tools Date: Tue, 20 Oct 2026 01:43:17 +0000 Subject: [PATCH] Add the layout of the indexer's manifests bucket: the key by digest and its value (issue 1357) --- go/delivery/manifests.go | 85 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 go/delivery/manifests.go diff --git a/go/delivery/manifests.go b/go/delivery/manifests.go new file mode 100644 index 0000000..d0d0bcd --- /dev/null +++ b/go/delivery/manifests.go @@ -0,0 +1,85 @@ +package delivery + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "unicode/utf8" +) + +// ManifestDigest is the hex sha256 of a manifest's bytes exactly as read at the commit: the key of a manifests value +// and the digest a Module and a candidate's module carry (novox/hq ADR 0429 clause 16). +func ManifestDigest(raw []byte) string { + sum := sha256.Sum256(raw) + return hex.EncodeToString(sum[:]) +} + +// ManifestsKey is the key, in ManifestsBucket, of a manifest: its digest, 64 lowercase hex digits. The indexer +// writes it once, by a create that fails when the key exists. +func ManifestsKey(digest string) (string, error) { + if !isHex64(digest) { + return "", fmt.Errorf("delivery: %q is not a manifest digest, 64 lowercase hex digits", digest) + } + return digest, nil +} + +// ParseManifestsKey is the inverse of ManifestsKey. +func ParseManifestsKey(key string) (digest string, err error) { + if digest, err = ManifestsKey(key); err != nil { + return "", fmt.Errorf("delivery: %q is not a manifests key: %w", key, err) + } + return digest, nil +} + +// Manifest is the value of a manifests key: the module and its manifest, the raw text of its module.json as read at the +// commit, byte for byte (ManifestDigest of JSON is the key). It is the one value of delivery that is larger than about +// 500 bytes: the largest manifest of the catalogue is about 21 KB. It is a string, not nested JSON, because nesting +// would re-write the manifest's spacing and so change its digest. +type Manifest struct { + V int `json:"v"` + Module string `json:"module"` + JSON string `json:"manifest"` +} + +func (m Manifest) check() error { + if err := checkName("module", m.Module); err != nil { + return err + } + if m.JSON == "" { + return fmt.Errorf("delivery: the manifest of %s is empty", m.Module) + } + if !utf8.ValidString(m.JSON) { + return fmt.Errorf("delivery: the manifest of %s is not UTF-8 text, which a value cannot hold byte for byte", m.Module) + } + if !json.Valid([]byte(m.JSON)) { + return fmt.Errorf("delivery: the manifest of %s is not JSON", m.Module) + } + return nil +} + +// EncodeManifest is m as JSON at Version; it refuses a module that is not a name, and a manifest that is empty, not +// UTF-8 or not JSON. +func EncodeManifest(m Manifest) ([]byte, error) { + if err := m.check(); err != nil { + return nil, err + } + m.V = Version + return json.Marshal(m) +} + +// DecodeManifest reads a manifests value; it refuses an unknown version (ErrUnknownVersion) and what EncodeManifest +// refuses. +func DecodeManifest(data []byte) (Manifest, error) { + var m Manifest + if err := decode(data, &m); err != nil { + return Manifest{}, err + } + if err := m.check(); err != nil { + return Manifest{}, err + } + return m, nil +} + +// SameManifest says whether two manifests are the same value, so a writer writes only when they differ. +func SameManifest(a, b Manifest) bool { return a == b }