delivery.SeriesPackage and SeriesVersion, field for field as the indexer writes them (name, the ecosystem name; package, <module>/<artifact>), with SeriesPackageKey, IsSeriesPackageKey and a checked encode and decode. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
106 lines
4.6 KiB
Go
106 lines
4.6 KiB
Go
package delivery
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// The packages state per series (design 51 *The versions*, design 32; ADR 0606 §1, ADR 0609 §1): beside the per-module
|
|
// values (PublishedPackages), the indexer keeps one key per package and series, `<name>.<series>` (SeriesPackageKey),
|
|
// holding the newest trunk version of that series the builder numbered and the newest a registry seat published. The
|
|
// indexer alone writes it; its readers (the builder, the checker, the build modules) take this value's form from here.
|
|
// A series key always holds a dot, which EscapeName never leaves in a per-module key (IsSeriesPackageKey).
|
|
|
|
// SeriesPackage is the value of a series key. Name is the package's name in its own ecosystem (a Go package's module
|
|
// path in its go.mod at the commit, an npm package's package.json name): the key is named by it, and a user's
|
|
// `packages` names it. Package is the module and artifact it is built from, `<module>/<artifact>`, by which the builder
|
|
// finds the name to number under; Series its `<major>.<minor>`. Seat is the registry seat it is published through;
|
|
// GoModule the Go module path a Go package is fetched under, empty for any other. Newest is the newest version of the
|
|
// series a seat published (what a user accepting the series builds against), Numbered the newest the builder numbered,
|
|
// published or not yet.
|
|
type SeriesPackage struct {
|
|
V int `json:"v"`
|
|
Name string `json:"name"`
|
|
Package string `json:"package,omitempty"`
|
|
Series string `json:"series"`
|
|
Seat string `json:"seat,omitempty"`
|
|
GoModule string `json:"go-module,omitempty"`
|
|
Newest *SeriesVersion `json:"newest,omitempty"`
|
|
Numbered *SeriesVersion `json:"numbered,omitempty"`
|
|
At time.Time `json:"at"`
|
|
}
|
|
|
|
// SeriesVersion is one version of a series: its number, the source commit it was built from, the digest and
|
|
// fingerprint. Contents are, for a series' newest of a Go package, its Go module's requirements' and packages' digests
|
|
// at Commit: what a user accepting the series is fingerprinted against; nil when the forge could not list it, and on a
|
|
// numbered version.
|
|
type SeriesVersion struct {
|
|
Version string `json:"version"`
|
|
Commit string `json:"commit,omitempty"`
|
|
Digest string `json:"digest,omitempty"`
|
|
Fingerprint string `json:"fingerprint,omitempty"`
|
|
Contents *PublishedPackages `json:"contents,omitempty"`
|
|
}
|
|
|
|
// SeriesPackageKey is the key of a package's series in the packages state: its ecosystem name as a key part and the
|
|
// series.
|
|
func SeriesPackageKey(name, series string) string { return EscapeName(name) + "." + series }
|
|
|
|
// IsSeriesPackageKey says whether a key of the packages state is a series key, not a per-module one.
|
|
func IsSeriesPackageKey(key string) bool { return strings.Contains(key, ".") }
|
|
|
|
// builtFrom says whether pkg is `<module>/<artifact>`: a module name (no slash) and an artifact named after it.
|
|
func builtFrom(pkg string) bool {
|
|
module, artifact, ok := strings.Cut(pkg, "/")
|
|
return ok && module != "" && artifact != ""
|
|
}
|
|
|
|
var seriesForm = regexp.MustCompile(`^[0-9]+\.[0-9]+$`)
|
|
|
|
func (s SeriesPackage) check() error {
|
|
switch {
|
|
case s.Name == "":
|
|
return fmt.Errorf("delivery: a series of %q names no package in its ecosystem", s.Package)
|
|
case !seriesForm.MatchString(s.Series):
|
|
return fmt.Errorf("delivery: the series %q of %s is no <major>.<minor>", s.Series, s.Name)
|
|
case s.Package != "" && !builtFrom(s.Package):
|
|
return fmt.Errorf("delivery: the series %s of %s is built from %q, which is no <module>/<artifact>", s.Series, s.Name, s.Package)
|
|
}
|
|
for which, v := range map[string]*SeriesVersion{"newest": s.Newest, "numbered": s.Numbered} {
|
|
if v != nil && v.Version == "" {
|
|
return fmt.Errorf("delivery: the %s version of %s %s names no version", which, s.Name, s.Series)
|
|
}
|
|
if v != nil && v.Contents != nil {
|
|
if err := v.Contents.check(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// EncodeSeriesPackage is s as JSON at Version; it refuses what a reader could not take.
|
|
func EncodeSeriesPackage(s SeriesPackage) ([]byte, error) {
|
|
if err := s.check(); err != nil {
|
|
return nil, err
|
|
}
|
|
s.V = Version
|
|
return json.Marshal(s)
|
|
}
|
|
|
|
// DecodeSeriesPackage reads a series key's value; it refuses an unknown version (ErrUnknownVersion) and what
|
|
// EncodeSeriesPackage refuses.
|
|
func DecodeSeriesPackage(data []byte) (SeriesPackage, error) {
|
|
var s SeriesPackage
|
|
if err := decode(data, &s); err != nil {
|
|
return SeriesPackage{}, err
|
|
}
|
|
if err := s.check(); err != nil {
|
|
return SeriesPackage{}, err
|
|
}
|
|
return s, nil
|
|
}
|