Files
mesh-delivery/modules/mesh-delivery-indexer/internal/index/catalog.go
T

180 lines
7.2 KiB
Go

package index
import (
"encoding/json"
"fmt"
"maps"
"slices"
"strings"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/views"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-delivery/sdk/graph"
)
// The seat mesh-catalog's verbs, answered from the indexer's own states (novox/hq ADR 0604 §1, §2): modules, module,
// provides and dependents read the modules state, the manifests state and the module graph, and write nothing. The
// indexer serves no request subject (ADR 0437 §4): its tools bundle serves them as <seat>.<verb>. Each answer keeps the
// field names of the old catalog module's tool it replaces (catalog_modules, catalog_module, catalog_provides,
// catalog_dependents): the answers are the seat's contract (catalogue.CatalogModules and the rest), built here.
// CatalogRead reads the states the seat's verbs answer from.
type CatalogRead struct {
// Modules is every value of the modules state, by module.
Modules func() (map[string]meshcatalog.ModulesByModuleValue, error)
// Manifest is the manifest kept under a digest in the manifests state, as its JSON.
Manifest func(digest string) (string, error)
// Graph is the module graph, by module.
Graph func() (graph.Graph, error)
// SeatDefinitions and Marks are the indexer's state of the seat definitions and of the revisions its views said it
// up to, which the verb seat-definitions answers from (seatdefs.go); nil answers the verb nothing.
SeatDefinitions, Marks views.Source
}
func entryOf(name string, m meshcatalog.ModulesByModuleValue) catalogue.CatalogEntry {
return catalogue.CatalogEntry{Module: name, Repository: m.Repository, Path: m.Path, Commit: m.Commit, FP: m.Fp2, None: m.None2}
}
// CatalogModulesAnswer is the verb modules: every module the modules state holds, by name.
func (c CatalogRead) CatalogModulesAnswer() (catalogue.CatalogModules, error) {
mods, err := c.Modules()
if err != nil {
return catalogue.CatalogModules{}, fmt.Errorf("the indexer's modules cannot be read: %w", err)
}
out := []catalogue.CatalogEntry{}
for _, name := range slices.Sorted(maps.Keys(mods)) {
out = append(out, entryOf(name, mods[name]))
}
return catalogue.CatalogModules{Modules: out}, nil
}
// CatalogModuleAnswer is the verb module: one module whole, its manifest included.
func (c CatalogRead) CatalogModuleAnswer(name string) (catalogue.CatalogOne, error) {
name = strings.TrimSpace(name)
if name == "" {
return catalogue.CatalogOne{}, fmt.Errorf("name the module")
}
mods, err := c.Modules()
if err != nil {
return catalogue.CatalogOne{}, fmt.Errorf("the indexer's modules cannot be read: %w", err)
}
m, ok := mods[name]
if !ok {
return catalogue.CatalogOne{}, fmt.Errorf("the trunk holds no module %s", name)
}
one := catalogue.CatalogModule{CatalogEntry: entryOf(name, m)}
if m.Manifest == "" {
return catalogue.CatalogOne{}, fmt.Errorf("the indexer keeps no manifest of %s", name)
}
raw, err := c.Manifest(m.Manifest)
if err != nil {
return catalogue.CatalogOne{}, fmt.Errorf("the manifest %s of %s cannot be read: %w", m.Manifest, name, err)
}
one.Manifest = json.RawMessage(raw)
return catalogue.CatalogOne{Module: one}, nil
}
// CatalogProvidesAnswer is the verb provides: the modules that provide a provision, each with its scope (a module provides
// its own name, at node scope), and the modules that require it, from the module graph's provision edges.
func (c CatalogRead) CatalogProvidesAnswer(provision string) (catalogue.CatalogProvides, error) {
provision = strings.TrimSpace(provision)
if provision == "" {
return catalogue.CatalogProvides{}, fmt.Errorf("name the provision")
}
g, mods, err := c.graphAndModules()
if err != nil {
return catalogue.CatalogProvides{}, err
}
provides, requires := []catalogue.CatalogEnd{}, []catalogue.CatalogEnd{}
for _, name := range g.Providers(provision) {
end := catalogue.CatalogEnd{Module: name, Commit: mods[name].Commit, Scope: catalogue.ScopeNode}
if name != provision {
end.Scope = c.scopeOf(mods[name], provision)
}
provides = append(provides, end)
}
for _, name := range g.Requirers(provision) {
requires = append(requires, catalogue.CatalogEnd{Module: name, Commit: mods[name].Commit})
}
return catalogue.CatalogProvides{Provision: provision, Provides: provides, Requires: requires}, nil
}
// scopeOf is the scope the module's manifest offers provision at, "" when its manifest cannot be read.
func (c CatalogRead) scopeOf(m meshcatalog.ModulesByModuleValue, provision string) string {
if m.Manifest == "" {
return ""
}
raw, err := c.Manifest(m.Manifest)
if err != nil {
return ""
}
man, err := catalogue.Decode([]byte(raw))
if err != nil {
return ""
}
for _, o := range man.Provides {
if o.Name == provision {
return o.At()
}
}
return ""
}
// CatalogDependentsAnswer is the verb dependents: the modules whose build edges reach the module, directly or through
// others, from the module graph.
func (c CatalogRead) CatalogDependentsAnswer(module string) (catalogue.CatalogDependents, error) {
module = strings.TrimSpace(module)
if module == "" {
return catalogue.CatalogDependents{}, fmt.Errorf("name the module")
}
g, mods, err := c.graphAndModules()
if err != nil {
return catalogue.CatalogDependents{}, err
}
out := []catalogue.CatalogEnd{}
for _, name := range g.Dependents(module) {
out = append(out, catalogue.CatalogEnd{Module: name, Commit: mods[name].Commit})
}
return catalogue.CatalogDependents{Module: module, Dependents: out}, nil
}
func (c CatalogRead) graphAndModules() (graph.Graph, map[string]meshcatalog.ModulesByModuleValue, error) {
g, err := c.Graph()
if err != nil {
return nil, nil, fmt.Errorf("the module graph cannot be read: %w", err)
}
mods, err := c.Modules()
if err != nil {
return nil, nil, fmt.Errorf("the indexer's modules cannot be read: %w", err)
}
return g, mods, nil
}
// CatalogTools are the seat mesh-catalog's verbs as tools named <seat>.<verb>, answered from the indexer's own states
// (novox/hq ADR 0604 §1, §2, ADR 0636 §4). The tool runner describes each with the seat's definition from the seat list
// (ADR 0635 §1), so the tools carry no description or input.
func CatalogTools(c CatalogRead) []stdio.Tool {
str := func(args map[string]any, k string) string { s, _ := args[k].(string); return s }
runs := map[string]func(map[string]any) (any, error){
meshcatalog.VerbModules: func(map[string]any) (any, error) { return c.CatalogModulesAnswer() },
meshcatalog.VerbModule: func(a map[string]any) (any, error) { return c.CatalogModuleAnswer(str(a, "module")) },
meshcatalog.VerbProvides: func(a map[string]any) (any, error) { return c.CatalogProvidesAnswer(str(a, "provision")) },
meshcatalog.VerbDependents: func(a map[string]any) (any, error) { return c.CatalogDependentsAnswer(str(a, "module")) },
}
if c.SeatDefinitions != nil {
runs[meshcatalog.VerbSeatDefinitions] = func(a map[string]any) (any, error) {
return SeatDefinitionsOf(c.SeatDefinitions, c.Marks, str(a, "seat"))
}
}
var out []stdio.Tool
for _, v := range meshcatalog.Verbs() {
if run, ok := runs[v]; ok {
out = append(out, stdio.Tool{Name: meshcatalog.Seat + "." + v, Run: run})
}
}
return out
}