Read seat definitions once: the indexer's reading becomes a module the checker requires (issue 1921)
The checking program's seat list parsed the definition format a second time beside the indexer's judge. The indexer's reading moves whole into its own sibling module seatfiles (a domain path, as takein), with ResolveRaw keeping every field as written for a request's seats; the judge and the take-in read through it, and the checker's rules only find the definitions and say why one is left out (the lead's ruling on D3, ADR 0634 §1). Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -29,3 +29,7 @@ replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun
|
||||
require git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
|
||||
|
||||
replace git.novox.be/novox/mesh-delivery/sdk => ../../sdk
|
||||
|
||||
require git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/seatfiles v0.0.0-00010101000000-000000000000
|
||||
|
||||
replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/seatfiles => ../mesh-delivery-indexer/seatfiles
|
||||
|
||||
@@ -8,11 +8,15 @@ import (
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/seatfiles"
|
||||
)
|
||||
|
||||
// The seat list a check makes from its heads (novox/hq ADR 0636 §4, ADR 0644 §1; the lead's ruling Q2 on milestone
|
||||
// 47/B0): the checking program built from the heads reads the seat definitions they carry and answers them resolved, so
|
||||
// the running checker hands the manifest validator and the composer bytes it does not read itself.
|
||||
// the running checker hands the manifest validator and the composer bytes it does not read itself. A definition is read
|
||||
// by the indexer's one reading of the format (package seatfiles); this file finds the definitions and says why one is
|
||||
// left out.
|
||||
|
||||
// SeatsVersion is the version of SeatsAnswer this program writes.
|
||||
const SeatsVersion = 1
|
||||
@@ -60,14 +64,12 @@ func SeatsOf(heads fs.FS) (SeatsAnswer, error) {
|
||||
}
|
||||
bySeat := map[string][]found{}
|
||||
for _, p := range files {
|
||||
raw, err := resolvedDefinition(heads, p)
|
||||
seat, raw, err := resolvedDefinition(heads, p)
|
||||
if err != nil {
|
||||
out.Left = append(out.Left, LeftOut{Path: p, Why: err.Error()})
|
||||
continue
|
||||
}
|
||||
var d definitionHead
|
||||
_ = json.Unmarshal(raw, &d)
|
||||
bySeat[d.Seat] = append(bySeat[d.Seat], found{p, raw})
|
||||
bySeat[seat] = append(bySeat[seat], found{p, raw})
|
||||
}
|
||||
for _, seat := range slices.Sorted(maps.Keys(bySeat)) {
|
||||
fs := bySeat[seat]
|
||||
@@ -86,57 +88,21 @@ func SeatsOf(heads fs.FS) (SeatsAnswer, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// definitionHead is what of a definition the list is made by: its seat, its scope and the parts it includes. The rest is
|
||||
// carried as written, for the manifest validator and the composer to read (milestone G gives this the domain SDK's one
|
||||
// reading).
|
||||
type definitionHead struct {
|
||||
Seat string `json:"seat"`
|
||||
Scope string `json:"scope"`
|
||||
Includes []string `json:"includes"`
|
||||
}
|
||||
|
||||
// resolvedDefinition is the definition at p with its parts' verbs and events put first and no includes, every field it
|
||||
// carries kept as written.
|
||||
func resolvedDefinition(heads fs.FS, p string) (json.RawMessage, error) {
|
||||
// resolvedDefinition is the definition at p resolved with the parts it includes from the `parts` directory beside its
|
||||
// `seats` directory, as the indexer's one reading of the format resolves it (seatfiles.ResolveRaw), every field kept as
|
||||
// written.
|
||||
func resolvedDefinition(heads fs.FS, p string) (string, json.RawMessage, error) {
|
||||
raw, err := fs.ReadFile(heads, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", nil, err
|
||||
}
|
||||
var d definitionHead
|
||||
if err := json.Unmarshal(raw, &d); err != nil {
|
||||
return nil, fmt.Errorf("a seat definition is not JSON: %w", err)
|
||||
}
|
||||
if d.Seat == "" || (d.Scope != "mesh" && d.Scope != "node") {
|
||||
return nil, fmt.Errorf("a seat definition names its seat and its scope (mesh or node), not %q and %q", d.Seat, d.Scope)
|
||||
}
|
||||
var whole map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &whole); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var verbs, events []json.RawMessage
|
||||
partsDir := path.Join(path.Dir(path.Dir(path.Dir(p))), "parts")
|
||||
for _, name := range d.Includes {
|
||||
d, out, err := seatfiles.ResolveRaw(raw, func(name string) ([]byte, error) {
|
||||
praw, err := fs.ReadFile(heads, path.Join(partsDir, name+".json"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the seat %s includes the part %q, which is not at %s", d.Seat, name, partsDir)
|
||||
return nil, fmt.Errorf("it is not at %s", partsDir)
|
||||
}
|
||||
var part struct{ Verbs, Events []json.RawMessage }
|
||||
if err := json.Unmarshal(praw, &part); err != nil {
|
||||
return nil, fmt.Errorf("the part %q of the seat %s is not JSON: %w", name, d.Seat, err)
|
||||
}
|
||||
verbs, events = append(verbs, part.Verbs...), append(events, part.Events...)
|
||||
}
|
||||
var own struct{ Verbs, Events []json.RawMessage }
|
||||
_ = json.Unmarshal(raw, &own)
|
||||
verbs, events = append(verbs, own.Verbs...), append(events, own.Events...)
|
||||
delete(whole, "includes")
|
||||
for k, list := range map[string][]json.RawMessage{"verbs": verbs, "events": events} {
|
||||
if len(list) == 0 {
|
||||
delete(whole, k)
|
||||
continue
|
||||
}
|
||||
b, _ := json.Marshal(list)
|
||||
whole[k] = b
|
||||
}
|
||||
return json.Marshal(whole)
|
||||
return praw, nil
|
||||
})
|
||||
return d.Seat, out, err
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/seatfiles v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
|
||||
)
|
||||
@@ -34,4 +35,6 @@ replace git.novox.be/novox/mesh-delivery/sdk => ../../sdk
|
||||
|
||||
replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein => ./takein
|
||||
|
||||
replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/seatfiles => ./seatfiles
|
||||
|
||||
replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg => ../mesh-delivery-forge-bridge/forgemsg
|
||||
|
||||
@@ -18,9 +18,8 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/seatfiles"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
|
||||
"mesh-delivery-indexer/judge"
|
||||
)
|
||||
|
||||
// definitionsDir is the directory of a contracts module's definitions that the module.json at dir names, from the first
|
||||
@@ -58,7 +57,7 @@ func (ix *Indexer) revisionSeats(ctx context.Context, ready []PendingEntry) ([]c
|
||||
if ix.ReadFile == nil || ix.Files == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var defs []judge.Definition
|
||||
var defs []seatfiles.Definition
|
||||
for _, e := range ready {
|
||||
if e.Covered || e.Forge.Merge == "" {
|
||||
continue
|
||||
@@ -75,7 +74,7 @@ func (ix *Indexer) revisionSeats(ctx context.Context, ready []PendingEntry) ([]c
|
||||
defs = append(defs, got...)
|
||||
}
|
||||
}
|
||||
seats, err := judge.Seats(defs)
|
||||
seats, err := seatfiles.Seats(defs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -84,7 +83,7 @@ func (ix *Indexer) revisionSeats(ctx context.Context, ready []PendingEntry) ([]c
|
||||
|
||||
// readDefinitions reads the definitions under a contracts module's definitions directory at a commit, resolved with their
|
||||
// parts.
|
||||
func (ix *Indexer) readDefinitions(ctx context.Context, repository, commit, defsDir string) ([]judge.Definition, error) {
|
||||
func (ix *Indexer) readDefinitions(ctx context.Context, repository, commit, defsDir string) ([]seatfiles.Definition, error) {
|
||||
file := func(p string) ([]byte, error) {
|
||||
if defsDir != "." && defsDir != "" {
|
||||
p = defsDir + "/" + p
|
||||
@@ -103,8 +102,8 @@ func (ix *Indexer) readDefinitions(ctx context.Context, repository, commit, defs
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing %s of %s at %.12s: %w", dir, repository, commit, err)
|
||||
}
|
||||
parts := map[string]judge.Part{}
|
||||
var out []judge.Definition
|
||||
parts := map[string]seatfiles.Part{}
|
||||
var out []seatfiles.Definition
|
||||
for _, p := range slices.Sorted(maps.Keys(listed[dir])) {
|
||||
seat, ok := strings.CutSuffix(p, "/seat.json")
|
||||
if !ok || seat == "" || strings.Contains(seat, "/") {
|
||||
@@ -114,7 +113,7 @@ func (ix *Indexer) readDefinitions(ctx context.Context, repository, commit, defs
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d, err := judge.ParseDefinition(raw)
|
||||
d, err := seatfiles.ParseDefinition(raw)
|
||||
if err == nil && d.Seat != seat {
|
||||
err = fmt.Errorf("it names the seat %q, not %q", d.Seat, seat)
|
||||
}
|
||||
@@ -129,11 +128,11 @@ func (ix *Indexer) readDefinitions(ctx context.Context, repository, commit, defs
|
||||
if perr != nil {
|
||||
return nil, perr
|
||||
}
|
||||
var part judge.Part
|
||||
var part seatfiles.Part
|
||||
if praw == nil {
|
||||
perr = errors.New("the commit holds no such part")
|
||||
} else {
|
||||
part, perr = judge.ParsePart(praw)
|
||||
part, perr = seatfiles.ParsePart(praw)
|
||||
}
|
||||
if perr != nil {
|
||||
err = fmt.Errorf("its part %s: %w", name, perr)
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/seatfiles"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/composer"
|
||||
)
|
||||
@@ -212,7 +213,7 @@ func Request(r composer.Request) (composer.Verdict, error) { return RequestWith(
|
||||
// the members' heads' definitions, so a manifest that serves or invokes what the group's definitions add is judged by
|
||||
// them. None is Request.
|
||||
func RequestWith(r composer.Request, seats [][]byte) (composer.Verdict, error) {
|
||||
over, err := SeatsOfFiles(seats)
|
||||
over, err := seatfiles.SeatsOfFiles(seats)
|
||||
if err != nil {
|
||||
return composer.Verdict{}, fmt.Errorf("a seat definition of the request: %w", err)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/seatfiles"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/overlay"
|
||||
@@ -155,8 +156,8 @@ func TestTheJudge(t *testing.T) {
|
||||
t.Fatalf("%s: the seats are not put back", row.name)
|
||||
}
|
||||
}
|
||||
parts := map[string]Part{"reads": {Verbs: []DefVerb{{Name: "manifest-reads"}}}}
|
||||
d, _ := ParseDefinition(defs[0])
|
||||
parts := map[string]seatfiles.Part{"reads": {Verbs: []seatfiles.DefVerb{{Name: "manifest-reads"}}}}
|
||||
d, _ := seatfiles.ParseDefinition(defs[0])
|
||||
if d, err := d.Resolve(parts); err != nil || len(d.Verbs) != 2 || d.Verbs[0].Name != "manifest-reads" {
|
||||
t.Errorf("a resolved definition: %+v %v", d, err)
|
||||
}
|
||||
|
||||
@@ -1,137 +1,15 @@
|
||||
package judge
|
||||
|
||||
// The seat definitions a revision carries (novox/hq ADR 0633 §1, §6; ADR 0635, ADR 0636 §4): a manifest is judged
|
||||
// against the seats of the revision it belongs to, never against the list this program was built with. A definition is
|
||||
// the contracts module's `seats/<seat>/seat.json` (design 26 *A seat's definition*), with the parts it includes from
|
||||
// `parts/<part>.json`. This reader takes of it only what a manifest is judged by (the seat's scope, what it delivers,
|
||||
// its verbs and events) and reads the rest leniently: the format's one strict reader (mesh-sdk seatdef) is not in the
|
||||
// mesh-sdk this judge is built with, and the indexer of the revision keeps the definitions whole.
|
||||
// The seat list a judge pass judges with: the seats a request or a revision carries (read by the indexer's one reading,
|
||||
// package seatfiles) put over the catalogue's list for that pass.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// Definition is the part of one seat's definition a manifest is judged by.
|
||||
type Definition struct {
|
||||
Format int `json:"format"`
|
||||
Seat string `json:"seat"`
|
||||
Scope string `json:"scope"`
|
||||
Decision string `json:"decision"`
|
||||
HolderDeclares bool `json:"holder-declares"`
|
||||
Delivers string `json:"delivers"`
|
||||
Replicated bool `json:"replicated"`
|
||||
PulledByKind bool `json:"pulled-by-kind"`
|
||||
Yields bool `json:"yields"`
|
||||
HeldByTheEngine bool `json:"held-by-the-engine"`
|
||||
Includes []string `json:"includes"`
|
||||
Verbs []DefVerb `json:"verbs"`
|
||||
Accepts []DefName `json:"accepts"`
|
||||
Events []DefName `json:"events"`
|
||||
}
|
||||
|
||||
// DefVerb is a verb of a definition.
|
||||
type DefVerb struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Optional bool `json:"optional"`
|
||||
Replaces []string `json:"replaces"`
|
||||
}
|
||||
|
||||
// DefName is an accepted message or an event of a definition.
|
||||
type DefName struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Part is a part a definition includes: verbs and events written once for the seats that share them.
|
||||
type Part struct {
|
||||
Part string `json:"part"`
|
||||
Verbs []DefVerb `json:"verbs"`
|
||||
Events []DefName `json:"events"`
|
||||
}
|
||||
|
||||
// ParseDefinition reads one definition; a file that is not one names a seat and a scope.
|
||||
func ParseDefinition(raw []byte) (Definition, error) {
|
||||
var d Definition
|
||||
if err := json.Unmarshal(raw, &d); err != nil {
|
||||
return Definition{}, fmt.Errorf("a seat definition is not JSON: %w", err)
|
||||
}
|
||||
if d.Seat == "" || (d.Scope != catalogue.ScopeMesh && d.Scope != catalogue.ScopeNode) {
|
||||
return Definition{}, fmt.Errorf("a seat definition names its seat and its scope (mesh or node), not %q and %q", d.Seat, d.Scope)
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// ParsePart reads one part.
|
||||
func ParsePart(raw []byte) (Part, error) {
|
||||
var p Part
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
return Part{}, fmt.Errorf("a part of a seat definition is not JSON: %w", err)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Resolve is the definition with the verbs and events of the parts it includes put first, as the indexer keeps it.
|
||||
func (d Definition) Resolve(parts map[string]Part) (Definition, error) {
|
||||
out := d
|
||||
out.Includes, out.Verbs, out.Events = nil, nil, nil
|
||||
for _, name := range d.Includes {
|
||||
p, ok := parts[name]
|
||||
if !ok {
|
||||
return Definition{}, fmt.Errorf("the seat %s includes the part %q, which is not given", d.Seat, name)
|
||||
}
|
||||
out.Verbs = append(out.Verbs, p.Verbs...)
|
||||
out.Events = append(out.Events, p.Events...)
|
||||
}
|
||||
out.Verbs = append(out.Verbs, d.Verbs...)
|
||||
out.Events = append(out.Events, d.Events...)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Seats are the seats of resolved definitions, in the catalogue's types; a seat its holder declares itself is left out.
|
||||
// A definition still naming parts it includes is refused: resolve it first.
|
||||
func Seats(defs []Definition) ([]catalogue.Seat, error) {
|
||||
var out []catalogue.Seat
|
||||
for _, d := range defs {
|
||||
if d.HolderDeclares {
|
||||
continue
|
||||
}
|
||||
if len(d.Includes) > 0 {
|
||||
return nil, fmt.Errorf("the seat %s names parts it includes: resolve it first", d.Seat)
|
||||
}
|
||||
s := catalogue.Seat{Name: d.Seat, Scope: d.Scope, Delivers: d.Delivers, Replicated: d.Replicated,
|
||||
PulledByKind: d.PulledByKind, Yields: d.Yields, HeldByTheEngine: d.HeldByTheEngine, Decision: d.Decision}
|
||||
for _, a := range d.Accepts {
|
||||
s.Accepts = append(s.Accepts, a.Name)
|
||||
}
|
||||
for _, e := range d.Events {
|
||||
s.Emits = append(s.Emits, e.Name)
|
||||
}
|
||||
for _, v := range d.Verbs {
|
||||
s.Serves = append(s.Serves, catalogue.Verb{Name: v.Name, Description: v.Description, Optional: v.Optional, Replaces: v.Replaces})
|
||||
}
|
||||
out = append(out, s)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// SeatsOfFiles are the seats of resolved definitions given as files, as a request carries them.
|
||||
func SeatsOfFiles(raws [][]byte) ([]catalogue.Seat, error) {
|
||||
defs := make([]Definition, 0, len(raws))
|
||||
for _, raw := range raws {
|
||||
d, err := ParseDefinition(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defs = append(defs, d)
|
||||
}
|
||||
return Seats(defs)
|
||||
}
|
||||
|
||||
// merged is base with each seat of over put in place of the seat of its name, or added: a definition is a seat's whole
|
||||
// contract. A verb the base seat holds keeps its input and output (a definition read here carries none).
|
||||
func merged(base, over []catalogue.Seat) []catalogue.Seat {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
module git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/seatfiles
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104002338-5a38a1221659
|
||||
@@ -0,0 +1,2 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104002338-5a38a1221659 h1:tK7MzxVm/ECNg1Yblhh1ynVEthbZCg2mSPW0wQZC0b0=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104002338-5a38a1221659/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
@@ -0,0 +1,181 @@
|
||||
// Package seatfiles is the indexer's one reading of the seat definitions a revision carries (novox/hq ADR 0633 §1, §6;
|
||||
// ADR 0635, ADR 0636 §4): a manifest is judged against the seats of the revision it belongs to, never against the list
|
||||
// the judge was built with. A definition is the contracts module's `seats/<seat>/seat.json` (design 26 *A seat's
|
||||
// definition*), with the parts it includes from `parts/<part>.json`. This reader takes of it only what a manifest is
|
||||
// judged by (the seat's scope, what it delivers, its verbs and events) and reads the rest leniently: the format's one
|
||||
// strict reader (mesh-sdk seatdef) is not in the mesh-sdk the judge is built with, and the indexer of the revision keeps
|
||||
// the definitions whole.
|
||||
//
|
||||
// It is the indexer's own package, a module of its own so that the checker's rules can require it (the lead's ruling on
|
||||
// milestone 47/B0, ADR 0634 §1: shared code lives with its owner): the indexer's judge and the checker's seat list read
|
||||
// definitions here and nowhere else. Milestone G's domain SDK reading replaces it.
|
||||
package seatfiles
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// Definition is the part of one seat's definition a manifest is judged by.
|
||||
type Definition struct {
|
||||
Format int `json:"format"`
|
||||
Seat string `json:"seat"`
|
||||
Scope string `json:"scope"`
|
||||
Decision string `json:"decision"`
|
||||
HolderDeclares bool `json:"holder-declares"`
|
||||
Delivers string `json:"delivers"`
|
||||
Replicated bool `json:"replicated"`
|
||||
PulledByKind bool `json:"pulled-by-kind"`
|
||||
Yields bool `json:"yields"`
|
||||
HeldByTheEngine bool `json:"held-by-the-engine"`
|
||||
Includes []string `json:"includes"`
|
||||
Verbs []DefVerb `json:"verbs"`
|
||||
Accepts []DefName `json:"accepts"`
|
||||
Events []DefName `json:"events"`
|
||||
}
|
||||
|
||||
// DefVerb is a verb of a definition.
|
||||
type DefVerb struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Optional bool `json:"optional"`
|
||||
Replaces []string `json:"replaces"`
|
||||
}
|
||||
|
||||
// DefName is an accepted message or an event of a definition.
|
||||
type DefName struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Part is a part a definition includes: verbs and events written once for the seats that share them.
|
||||
type Part struct {
|
||||
Part string `json:"part"`
|
||||
Verbs []DefVerb `json:"verbs"`
|
||||
Events []DefName `json:"events"`
|
||||
}
|
||||
|
||||
// ParseDefinition reads one definition; a file that is not one names a seat and a scope.
|
||||
func ParseDefinition(raw []byte) (Definition, error) {
|
||||
var d Definition
|
||||
if err := json.Unmarshal(raw, &d); err != nil {
|
||||
return Definition{}, fmt.Errorf("a seat definition is not JSON: %w", err)
|
||||
}
|
||||
if d.Seat == "" || (d.Scope != catalogue.ScopeMesh && d.Scope != catalogue.ScopeNode) {
|
||||
return Definition{}, fmt.Errorf("a seat definition names its seat and its scope (mesh or node), not %q and %q", d.Seat, d.Scope)
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// ParsePart reads one part.
|
||||
func ParsePart(raw []byte) (Part, error) {
|
||||
var p Part
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
return Part{}, fmt.Errorf("a part of a seat definition is not JSON: %w", err)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Resolve is the definition with the verbs and events of the parts it includes put first, as the indexer keeps it.
|
||||
func (d Definition) Resolve(parts map[string]Part) (Definition, error) {
|
||||
out := d
|
||||
out.Includes, out.Verbs, out.Events = nil, nil, nil
|
||||
for _, name := range d.Includes {
|
||||
p, ok := parts[name]
|
||||
if !ok {
|
||||
return Definition{}, fmt.Errorf("the seat %s includes the part %q, which is not given", d.Seat, name)
|
||||
}
|
||||
out.Verbs = append(out.Verbs, p.Verbs...)
|
||||
out.Events = append(out.Events, p.Events...)
|
||||
}
|
||||
out.Verbs = append(out.Verbs, d.Verbs...)
|
||||
out.Events = append(out.Events, d.Events...)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Seats are the seats of resolved definitions, in the catalogue's types; a seat its holder declares itself is left out.
|
||||
// A definition still naming parts it includes is refused: resolve it first.
|
||||
func Seats(defs []Definition) ([]catalogue.Seat, error) {
|
||||
var out []catalogue.Seat
|
||||
for _, d := range defs {
|
||||
if d.HolderDeclares {
|
||||
continue
|
||||
}
|
||||
if len(d.Includes) > 0 {
|
||||
return nil, fmt.Errorf("the seat %s names parts it includes: resolve it first", d.Seat)
|
||||
}
|
||||
s := catalogue.Seat{Name: d.Seat, Scope: d.Scope, Delivers: d.Delivers, Replicated: d.Replicated,
|
||||
PulledByKind: d.PulledByKind, Yields: d.Yields, HeldByTheEngine: d.HeldByTheEngine, Decision: d.Decision}
|
||||
for _, a := range d.Accepts {
|
||||
s.Accepts = append(s.Accepts, a.Name)
|
||||
}
|
||||
for _, e := range d.Events {
|
||||
s.Emits = append(s.Emits, e.Name)
|
||||
}
|
||||
for _, v := range d.Verbs {
|
||||
s.Serves = append(s.Serves, catalogue.Verb{Name: v.Name, Description: v.Description, Optional: v.Optional, Replaces: v.Replaces})
|
||||
}
|
||||
out = append(out, s)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// SeatsOfFiles are the seats of resolved definitions given as files, as a request carries them.
|
||||
func SeatsOfFiles(raws [][]byte) ([]catalogue.Seat, error) {
|
||||
defs := make([]Definition, 0, len(raws))
|
||||
for _, raw := range raws {
|
||||
d, err := ParseDefinition(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defs = append(defs, d)
|
||||
}
|
||||
return Seats(defs)
|
||||
}
|
||||
|
||||
// ResolveRaw is the definition raw resolved with the parts it includes, each read by part, every field it carries kept as
|
||||
// written: the verbs and events of its parts put first, then its own, and no includes. It reads raw and every part as
|
||||
// ParseDefinition and ParsePart do, and answers the definition read with it. It is how a request's `seats` carry a
|
||||
// definition whole (ADR 0636 §4).
|
||||
func ResolveRaw(raw []byte, part func(name string) ([]byte, error)) (Definition, json.RawMessage, error) {
|
||||
d, err := ParseDefinition(raw)
|
||||
if err != nil {
|
||||
return Definition{}, nil, err
|
||||
}
|
||||
var whole map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &whole); err != nil {
|
||||
return Definition{}, nil, err
|
||||
}
|
||||
type listed struct{ Verbs, Events []json.RawMessage }
|
||||
var verbs, events []json.RawMessage
|
||||
for _, name := range d.Includes {
|
||||
praw, err := part(name)
|
||||
if err != nil {
|
||||
return Definition{}, nil, fmt.Errorf("the seat %s includes the part %q: %w", d.Seat, name, err)
|
||||
}
|
||||
var l listed
|
||||
if _, err := ParsePart(praw); err != nil {
|
||||
return Definition{}, nil, fmt.Errorf("the part %q of the seat %s: %w", name, d.Seat, err)
|
||||
}
|
||||
_ = json.Unmarshal(praw, &l)
|
||||
verbs, events = append(verbs, l.Verbs...), append(events, l.Events...)
|
||||
}
|
||||
var own listed
|
||||
_ = json.Unmarshal(raw, &own)
|
||||
verbs, events = append(verbs, own.Verbs...), append(events, own.Events...)
|
||||
delete(whole, "includes")
|
||||
for k, list := range map[string][]json.RawMessage{"verbs": verbs, "events": events} {
|
||||
if len(list) == 0 {
|
||||
delete(whole, k)
|
||||
continue
|
||||
}
|
||||
b, err := json.Marshal(list)
|
||||
if err != nil {
|
||||
return Definition{}, nil, err
|
||||
}
|
||||
whole[k] = b
|
||||
}
|
||||
out, err := json.Marshal(whole)
|
||||
return d, out, err
|
||||
}
|
||||
Reference in New Issue
Block a user