143 lines
5.8 KiB
Go
143 lines
5.8 KiB
Go
package meshcatalogclient
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
|
|
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
|
"git.novox.be/novox/mesh-sdk/go/keys"
|
|
)
|
|
|
|
// Module is the module that holds the seat mesh-catalog, and the two states of its others read, by the address a
|
|
// manifest's `reads` and a first view name them: the modules the trunk holds and their manifests. A reader names them here,
|
|
// never by the holder's literal name.
|
|
const (
|
|
Module = "mesh-delivery-indexer"
|
|
ModulesRead = Module + ".modules"
|
|
ManifestsRead = Module + ".manifests"
|
|
)
|
|
|
|
// RootPath is a `modules` value's path when the module's manifest is its repository's root.
|
|
const RootPath = "."
|
|
|
|
// SourcePrefix1 opens the fingerprint of the old form a `modules` value may still carry in `fp`.
|
|
const SourcePrefix1 = "src1:"
|
|
|
|
// ModulesKey is the key of a `modules` value: the module's name.
|
|
func ModulesKey(module string) (string, error) {
|
|
if err := keys.CheckName("module", module); err != nil {
|
|
return "", err
|
|
}
|
|
return seat.ModulesByModuleKey(module), nil
|
|
}
|
|
|
|
// ParseModulesKey is the inverse of ModulesKey.
|
|
func ParseModulesKey(key string) (string, error) {
|
|
module, err := ModulesKey(key)
|
|
if err != nil {
|
|
return "", fmt.Errorf("meshcatalogclient: %q is not a modules key: %w", key, err)
|
|
}
|
|
return module, nil
|
|
}
|
|
|
|
// CheckModule refuses a `modules` value that contradicts itself: both or neither of a fingerprint and the reason for none,
|
|
// of either form, an old fingerprint not of the form `src1:`, kept inputs without a manifest digest, a verdict the indexer
|
|
// does not write, problems without a refusal (or a refusal without problems), a path that is not clean inside the module's
|
|
// directory, and a fingerprint or a digest not of its form.
|
|
func CheckModule(m seat.ModulesByModuleValue) error {
|
|
switch {
|
|
case m.Fp != "" && m.None != "":
|
|
return fmt.Errorf("meshcatalogclient: a module has both a fingerprint and a reason for none")
|
|
case m.Fp == "" && m.None == "" && m.Fp2 == "" && m.None2 == "":
|
|
return fmt.Errorf("meshcatalogclient: a module has neither a fingerprint nor a reason for none, of either form")
|
|
case m.Fp != "" && !strings.HasPrefix(m.Fp, SourcePrefix1):
|
|
return fmt.Errorf("meshcatalogclient: the module's fingerprint %q does not start with %s", m.Fp, SourcePrefix1)
|
|
case m.Fp2 != "" && m.None2 != "":
|
|
return fmt.Errorf("meshcatalogclient: a module has both fp2 and none2")
|
|
}
|
|
for language, t := range m.Toolchains {
|
|
if t.Version == "" || t.Recipe == "" {
|
|
return fmt.Errorf("meshcatalogclient: the module's toolchain of %q needs its version and its recipe version", language)
|
|
}
|
|
}
|
|
if m.Inputs2 != nil && m.Manifest == "" {
|
|
return fmt.Errorf("meshcatalogclient: a module keeps new-form inputs but has no manifest digest")
|
|
}
|
|
for artifact, paths := range m.Paths {
|
|
if artifact == "" || len(paths) == 0 {
|
|
return fmt.Errorf("meshcatalogclient: the module's named paths of the artifact %q are empty or name no artifact", artifact)
|
|
}
|
|
for _, p := range paths {
|
|
if !cleanInside(p) {
|
|
return fmt.Errorf("meshcatalogclient: the artifact %q names the path %q, not a clean path inside the module's directory", artifact, p)
|
|
}
|
|
}
|
|
}
|
|
if in := m.Inputs2; in != nil {
|
|
if f := in.ImportForm; f != "" && (len(in.Imports) == 0 || !strings.HasPrefix(f, SourcePrefix2) || !keys.IsHex64(f[len(SourcePrefix2):])) {
|
|
return fmt.Errorf("meshcatalogclient: the kept import form %q is set only with imports, and is %s and 64 hex digits", f, SourcePrefix2)
|
|
}
|
|
for p, object := range in.Paths {
|
|
if !cleanInside(p) {
|
|
return fmt.Errorf("meshcatalogclient: the named path %q is not a clean path inside the module's directory", p)
|
|
}
|
|
if object == "" {
|
|
return fmt.Errorf("meshcatalogclient: the named path %q names no git object", p)
|
|
}
|
|
}
|
|
}
|
|
switch {
|
|
case m.Verdict != "" && m.Verdict != VerdictAccepted && m.Verdict != VerdictRefused:
|
|
return fmt.Errorf("meshcatalogclient: the module's verdict %q is neither %s nor %s", m.Verdict, VerdictAccepted, VerdictRefused)
|
|
case len(m.Unknown) > 0 && m.Verdict != VerdictRefused:
|
|
return fmt.Errorf("meshcatalogclient: a module's unknown fields are named only with the refusal a wait is written as")
|
|
case m.Verdict != "" && m.Manifest == "":
|
|
return fmt.Errorf("meshcatalogclient: a module carries a verdict and no manifest digest")
|
|
case (m.Verdict == VerdictRefused) != (len(m.Problems) > 0):
|
|
return fmt.Errorf("meshcatalogclient: a module's problems are named only with a refusal, and a refusal names them")
|
|
case m.Verdict == VerdictRefused && len(m.Warnings) > 0:
|
|
return fmt.Errorf("meshcatalogclient: a refused module warns: a refusal names problems")
|
|
}
|
|
if m.Fp2 != "" {
|
|
if _, err := KeyPart(m.Fp2); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if m.Manifest != "" {
|
|
if _, err := ManifestsKey(m.Manifest); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func cleanInside(p string) bool {
|
|
return p != "" && p == path.Clean(p) && !strings.HasPrefix(p, "/") && p != ".." && !strings.HasPrefix(p, "../")
|
|
}
|
|
|
|
// EncodeModule is m as JSON at the kept values' version; it refuses what CheckModule refuses.
|
|
func EncodeModule(m seat.ModulesByModuleValue) ([]byte, error) {
|
|
if err := CheckModule(m); err != nil {
|
|
return nil, err
|
|
}
|
|
m.V = keys.Version
|
|
return json.Marshal(m)
|
|
}
|
|
|
|
// DecodeModule reads a `modules` value; it refuses an unknown version (keys.ErrUnknownVersion) and what EncodeModule refuses.
|
|
func DecodeModule(data []byte) (seat.ModulesByModuleValue, error) {
|
|
if err := keys.CheckVersion(data); err != nil {
|
|
return seat.ModulesByModuleValue{}, err
|
|
}
|
|
var m seat.ModulesByModuleValue
|
|
if err := json.Unmarshal(data, &m); err != nil {
|
|
return seat.ModulesByModuleValue{}, fmt.Errorf("meshcatalogclient: value does not fit its shape: %w", err)
|
|
}
|
|
if err := CheckModule(m); err != nil {
|
|
return seat.ModulesByModuleValue{}, err
|
|
}
|
|
return m, nil
|
|
}
|