190 lines
8.0 KiB
Go
190 lines
8.0 KiB
Go
package index
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
|
"git.novox.be/novox/mesh-delivery/sdk/trunk"
|
|
"git.novox.be/novox/mesh-sdk/go/keys"
|
|
)
|
|
|
|
// The values of the state packages (novox/hq ADR 0593 §2, ADR 0606 §1): the contract's types, written by the indexer
|
|
// alone, so their writing rules are the indexer's (ADR 0634 §1: a holder's own logic stays with it).
|
|
|
|
// PackagesKey is the key of a mesh Go module (its module path) in the packages state: the path as a key part. It refuses
|
|
// a path that is no mesh Go module's.
|
|
func PackagesKey(module string) (string, error) {
|
|
if _, _, ok := trunk.PackageRepository(module); !ok {
|
|
return "", fmt.Errorf("index: %q is no mesh Go module's path (it starts with %s)", module, trunk.ForgeModulePrefix)
|
|
}
|
|
return keys.EscapeName(module), nil
|
|
}
|
|
|
|
func checkPublished(p meshcatalog.PackagesByModuleValue) error {
|
|
pkgs := make([][2]string, len(p.Packages))
|
|
for i, d := range p.Packages {
|
|
pkgs[i] = [2]string{d.Package, d.Digest}
|
|
}
|
|
return checkPublishedFields(p.Module, p.Repository, p.Dir, p.Commit, p.Requires, p.At, pkgs)
|
|
}
|
|
|
|
var commitFull = regexp.MustCompile(`^[0-9a-f]{40}$`)
|
|
|
|
// checkPublishedFields is the rule of a published module's value, over its fields (a series' contents hold the same).
|
|
func checkPublishedFields(module, repository, dir, commit, requires string, at *time.Time, packages [][2]string) error {
|
|
want, _, ok := trunk.PackageRepository(module)
|
|
switch {
|
|
case !ok:
|
|
return fmt.Errorf("index: %q is no mesh Go module's path (it starts with %s)", module, trunk.ForgeModulePrefix)
|
|
case !trunk.SameRepository(repository, want):
|
|
return fmt.Errorf("index: the module %s is published from %s, and the value names the repository %q", module, want, repository)
|
|
case dir == "":
|
|
return fmt.Errorf("index: the published module %s names no directory (%q for the repository's root)", module, meshcatalogclient.RootPath)
|
|
case !commitFull.MatchString(commit):
|
|
return fmt.Errorf("index: the published module %s names the commit %q, which is no full commit", module, commit)
|
|
case requires == "":
|
|
return fmt.Errorf("index: the published module %s names no digest of its requirements", module)
|
|
case at == nil || at.IsZero():
|
|
return fmt.Errorf("index: the published module %s names no time", module)
|
|
}
|
|
for i, d := range packages {
|
|
if d[0] != module && !strings.HasPrefix(d[0], module+"/") {
|
|
return fmt.Errorf("index: the package %q is not in the module %s", d[0], module)
|
|
}
|
|
if d[1] == "" {
|
|
return fmt.Errorf("index: the package %s names no digest", d[0])
|
|
}
|
|
if i > 0 && packages[i-1][0] >= d[0] {
|
|
return fmt.Errorf("index: the packages of %s are not sorted and each once (%s after %s)", module, d[0], packages[i-1][0])
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// EncodePublishedPackages is p as JSON at the kept values' version; it refuses what a reader could not take.
|
|
func EncodePublishedPackages(p meshcatalog.PackagesByModuleValue) ([]byte, error) {
|
|
if err := checkPublished(p); err != nil {
|
|
return nil, err
|
|
}
|
|
p.V = keys.Version
|
|
return json.Marshal(p)
|
|
}
|
|
|
|
// DecodePublishedPackages reads a per-module packages value; it refuses an unknown version (keys.ErrUnknownVersion) and
|
|
// what EncodePublishedPackages refuses.
|
|
func DecodePublishedPackages(data []byte) (meshcatalog.PackagesByModuleValue, error) {
|
|
if err := keys.CheckVersion(data); err != nil {
|
|
return meshcatalog.PackagesByModuleValue{}, err
|
|
}
|
|
var p meshcatalog.PackagesByModuleValue
|
|
if err := json.Unmarshal(data, &p); err != nil {
|
|
return meshcatalog.PackagesByModuleValue{}, fmt.Errorf("index: a packages value does not fit its shape: %w", err)
|
|
}
|
|
if err := checkPublished(p); err != nil {
|
|
return meshcatalog.PackagesByModuleValue{}, err
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// 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 keys.EscapeName(name) + "." + series }
|
|
|
|
// IsSeriesPackageKey says whether a key of the packages state is a series key, not a per-module one (a series key always
|
|
// holds a dot, which keys.EscapeName never leaves in a per-module key).
|
|
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 checkSeries(s meshcatalog.PackagesByPackageSeriesValue) error {
|
|
switch {
|
|
case s.Name == "":
|
|
return fmt.Errorf("index: a series of %q names no package in its ecosystem", s.Package)
|
|
case !seriesForm.MatchString(s.Series):
|
|
return fmt.Errorf("index: the series %q of %s is no <major>.<minor>", s.Series, s.Name)
|
|
case s.Package != "" && !builtFrom(s.Package):
|
|
return fmt.Errorf("index: the series %s of %s is built from %q, which is no <module>/<artifact>", s.Series, s.Name, s.Package)
|
|
}
|
|
if v := s.Newest; v != nil {
|
|
if v.Version == "" {
|
|
return fmt.Errorf("index: the newest version of %s %s names no version", s.Name, s.Series)
|
|
}
|
|
if c := v.Contents; c != nil {
|
|
pkgs := make([][2]string, len(c.Packages))
|
|
for i, d := range c.Packages {
|
|
pkgs[i] = [2]string{d.Package, d.Digest}
|
|
}
|
|
if err := checkPublishedFields(c.Module, c.Repository, c.Dir, c.Commit, c.Requires, c.At, pkgs); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
if v := s.Numbered; v != nil {
|
|
if v.Version == "" {
|
|
return fmt.Errorf("index: the numbered version of %s %s names no version", s.Name, s.Series)
|
|
}
|
|
if c := v.Contents; c != nil {
|
|
pkgs := make([][2]string, len(c.Packages))
|
|
for i, d := range c.Packages {
|
|
pkgs[i] = [2]string{d.Package, d.Digest}
|
|
}
|
|
if err := checkPublishedFields(c.Module, c.Repository, c.Dir, c.Commit, c.Requires, c.At, pkgs); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// EncodeSeriesPackage is s as JSON at the kept values' version; it refuses what a reader could not take.
|
|
func EncodeSeriesPackage(s meshcatalog.PackagesByPackageSeriesValue) ([]byte, error) {
|
|
if err := checkSeries(s); err != nil {
|
|
return nil, err
|
|
}
|
|
s.V = keys.Version
|
|
return json.Marshal(s)
|
|
}
|
|
|
|
// DecodeSeriesPackage reads a series key's value; it refuses an unknown version and what EncodeSeriesPackage refuses.
|
|
func DecodeSeriesPackage(data []byte) (meshcatalog.PackagesByPackageSeriesValue, error) {
|
|
if err := keys.CheckVersion(data); err != nil {
|
|
return meshcatalog.PackagesByPackageSeriesValue{}, err
|
|
}
|
|
var s meshcatalog.PackagesByPackageSeriesValue
|
|
if err := json.Unmarshal(data, &s); err != nil {
|
|
return meshcatalog.PackagesByPackageSeriesValue{}, fmt.Errorf("index: a series value does not fit its shape: %w", err)
|
|
}
|
|
if err := checkSeries(s); err != nil {
|
|
return meshcatalog.PackagesByPackageSeriesValue{}, err
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// publishedOfContents is a series version's contents as the value of a per-module packages key (the same fields).
|
|
func publishedOfContents(c meshcatalog.PackagesByPackageSeriesValueNewestContents) meshcatalog.PackagesByModuleValue {
|
|
p := meshcatalog.PackagesByModuleValue{At: c.At, Commit: c.Commit, Dir: c.Dir, Module: c.Module, Repository: c.Repository, Requires: c.Requires, V: c.V}
|
|
for _, d := range c.Packages {
|
|
p.Packages = append(p.Packages, meshcatalog.PackagesByModuleValuePackagesItem{Package: d.Package, Digest: d.Digest})
|
|
}
|
|
return p
|
|
}
|
|
|
|
// contentsOfPublished is the value of a per-module packages key as a series version's contents.
|
|
func contentsOfPublished(p meshcatalog.PackagesByModuleValue) *meshcatalog.PackagesByPackageSeriesValueNewestContents {
|
|
c := &meshcatalog.PackagesByPackageSeriesValueNewestContents{At: p.At, Commit: p.Commit, Dir: p.Dir, Module: p.Module, Repository: p.Repository, Requires: p.Requires, V: p.V}
|
|
for _, d := range p.Packages {
|
|
c.Packages = append(c.Packages, meshcatalog.PackagesByPackageSeriesValueNewestContentsPackagesItem{Package: d.Package, Digest: d.Digest})
|
|
}
|
|
return c
|
|
}
|