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, `.` (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, `/`, by which the builder // finds the name to number under; Series its `.`. 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 `/`: 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 .", 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 /", 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 }