Files
mesh-sdk/go/catalogue/optional_requirements.go
T
jschoubben d3e10f68c5 A bus-state entry stays off Requires, which names provisions (issue 1966 S1)
A local name such as records is no provision: on Requires it would send
the placement looking for a provider nothing offers (the lead's ruling on
issue 1966 B1). BusState alone holds the typed entries.
2026-11-03 13:26:27 +01:00

139 lines
5.2 KiB
Go

package catalogue
import (
"bytes"
"encoding/json"
"fmt"
"sort"
"strings"
)
// OnlyWhenBound is a resource's field naming the optional requirement it is delivered only while
// bound (ADR 0330 §5). Said in the catalogue and dropped before the declaration is sent: the
// node-engine parses strictly and knows no such field.
const OnlyWhenBound = "only-when-bound"
// requirementForm is one entry of `requires` in its long form.
type requirementForm struct {
Name string `json:"name"`
Optional bool `json:"optional,omitempty"`
// Shares is who else may use the consumer's bucket (BucketShare, novox/hq ADR 0468 §8), on s3-bucket only.
Shares []BucketShare `json:"shares,omitempty"`
}
// requiresRead is `requires` as readRequires read it.
type requiresRead struct {
names []string
optional map[string]bool
shares map[string][]BucketShare
// busState are the entries asking for bus state (BusStateEntry), never named in names, which name provisions; aside
// the paths of the keys they had that this package does not know, set aside (ADR 0578 §3).
busState []BusStateEntry
aside []string
}
// readRequires reads `requires` in its forms — a bare name, {name, optional}, or a bus-state entry (BusStateEntry,
// novox/hq ADR 0595 §1) — into the names of the provisions, which of them are optional, and the bus-state entries,
// whose local names stay off the names: a local name is no provision, and nothing provides it.
func readRequires(raw json.RawMessage) (requiresRead, error) {
var entries []json.RawMessage
if err := json.Unmarshal(raw, &entries); err != nil {
return requiresRead{}, fmt.Errorf("requires: a list of names, or of {name, optional}: %w", err)
}
var names []string
optional := map[string]bool{}
var shares map[string][]BucketShare
var busState []BusStateEntry
var aside []string
for i, e := range entries {
var plain string
if err := json.Unmarshal(e, &plain); err == nil {
names = append(names, plain)
continue
}
if isBusStateEntry(e) {
entry, unknown, err := readBusStateEntry(e)
if err != nil {
return requiresRead{}, fmt.Errorf("requires[%d]: a bus-state entry is {name, storage, template, options, configuration, types} (novox/hq ADR 0595 §1): %w", i, err)
}
busState = append(busState, entry)
for _, path := range unknown {
aside = append(aside, fmt.Sprintf("requires[%d]%s", i, path))
}
continue
}
var full requirementForm
dec := json.NewDecoder(bytes.NewReader(e))
dec.DisallowUnknownFields()
if err := dec.Decode(&full); err != nil {
return requiresRead{}, fmt.Errorf("requires[%d]: a name, or {name, optional} (novox/hq ADR 0330): %w", i, err)
}
if strings.TrimSpace(full.Name) == "" {
return requiresRead{}, fmt.Errorf("requires[%d] names no requirement: {name, optional}", i)
}
names = append(names, full.Name)
if err := checkBucketShares(full.Name, full.Shares); err != nil {
return requiresRead{}, fmt.Errorf("requires[%d]: %w", i, err)
}
if len(full.Shares) > 0 {
if shares == nil {
shares = map[string][]BucketShare{}
}
shares[full.Name] = full.Shares
}
if full.Optional {
optional[full.Name] = true
}
}
if len(optional) == 0 {
optional = nil
}
return requiresRead{names: names, optional: optional, shares: shares, busState: busState, aside: aside}, nil
}
// OptionalRequiresKey is where a stored manifest keeps which of its requirements are optional: beside
// `requires`, which stays a plain list of names (mesh-controller #281's review). A controller older than
// optional requirements reads `requires` as names; the long form inside it failed that read, and with it
// the whole catalogue, so a rollback below this controller would have composed no machine. An older
// controller sets this unknown key aside instead and leaves only the module that carries it out. A
// manifest may be written either way; it is stored this way.
const OptionalRequiresKey = "optional-requires"
// readOptionalRequires reads the stored form's list of optional requirements.
func readOptionalRequires(raw json.RawMessage) (map[string]bool, error) {
var names []string
if err := json.Unmarshal(raw, &names); err != nil {
return nil, fmt.Errorf("%s: a list of the names in `requires` that are optional: %w", OptionalRequiresKey, err)
}
out := map[string]bool{}
for _, n := range names {
out[n] = true
}
return out, nil
}
// optionalRequiresList is the stored form's list: the optional requirements, sorted.
func optionalRequiresList(m Manifest) []string {
var out []string
for name, optional := range m.OptionalRequires {
if optional {
out = append(out, name)
}
}
sort.Strings(out)
return out
}
// IsOptional is whether this module requires the name optionally (ADR 0330 §1). A name it contributes
// to without requiring, or requires as a bare name, is hard.
func (m Manifest) IsOptional(requirement string) bool { return m.OptionalRequires[requirement] }
// Absent is one optional requirement of one consumer that nothing assigned answers (ADR 0330 §3):
// left unbound rather than refused, and listed by `plan` and `node` (§7).
type Absent struct {
// Consumer is the module that requires it optionally.
Consumer string `json:"consumer"`
// Requirement is what it requires.
Requirement string `json:"requirement"`
}