The grants part composes the bus's user list with the composer the controller uses (ADR 0547 §5), copied rather than moved: the controller is not changed and keeps its own until it loses the lease. Left out: the bus client, the broker's address from the environment and the client's two methods; the lease's two subjects are copied in. One table test holds the copy to four meshes' lists and the server golden, written by the controller's own internal/broker from the same fixtures (testdata/controller-user-lists.json).
126 lines
3.8 KiB
Go
126 lines
3.8 KiB
Go
package buscompose
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// Do the emitters and the consumers of a catalogue agree?
|
|
//
|
|
// **The check that was missing** (novox/hq 04-ISSUES/127). Every manifest was individually
|
|
// well-formed and every derivation individually correct, and no cross-module subscription in the
|
|
// mesh matched anything: a consumer's declaration derived into a namespace nobody publishes to.
|
|
// Nothing failed, because a subscription that matches nothing is not an error — it is silence.
|
|
//
|
|
// The comparison has to be over the whole catalogue, because the two halves live in different
|
|
// manifests, and it cannot simply demand that every consumed event have a live emitter: a module
|
|
// may be installed long before the one whose events it wants. So the rule is narrower and still
|
|
// catches this: **where the emitter is present, it must emit what the consumer asked for.**
|
|
|
|
// AConsumer is one module's interest in another's events, as this check needs it.
|
|
type AConsumer struct {
|
|
Module string
|
|
Consumes []string
|
|
}
|
|
|
|
// AnEmitter is one module's events.
|
|
type AnEmitter struct {
|
|
Module string
|
|
Emits []string
|
|
}
|
|
|
|
// Disagreements are the consumed events whose emitter is in the catalogue and does not emit them.
|
|
//
|
|
// Returned as sentences rather than as structs: every one of them is read by a person deciding
|
|
// whether a manifest or a catalogue is wrong, and a pair of names without the reason is a puzzle.
|
|
func Disagreements(emitters []AnEmitter, consumers []AConsumer, seats []DeclaredSeat) []string {
|
|
emits := map[string]map[string]bool{}
|
|
for _, e := range emitters {
|
|
if emits[e.Module] == nil {
|
|
emits[e.Module] = map[string]bool{}
|
|
}
|
|
for _, name := range e.Emits {
|
|
emits[e.Module][name] = true
|
|
}
|
|
}
|
|
// A seat's events are published by its holder under the seat's name, so a consumer naming the
|
|
// seat is naming something real even though no module declares it as its own.
|
|
for _, s := range seats {
|
|
if len(s.Emits) == 0 {
|
|
continue
|
|
}
|
|
if emits[s.Name] == nil {
|
|
emits[s.Name] = map[string]bool{}
|
|
}
|
|
for _, name := range s.Emits {
|
|
emits[s.Name][name] = true
|
|
}
|
|
}
|
|
|
|
var out []string
|
|
for _, c := range consumers {
|
|
for _, pattern := range c.Consumes {
|
|
emitter, event, named := strings.Cut(pattern, ".")
|
|
// Every event from everyone, or every event from one module: both are deliberate and
|
|
// neither names a particular event to check.
|
|
if !named || emitter == "*" || emitter == catalogueTheRest || event == catalogueTheRest {
|
|
continue
|
|
}
|
|
known, present := emits[emitter]
|
|
if !present {
|
|
// Not installed here, which is ordinary: a module lives in its own repository and
|
|
// may be registered later. Nothing to compare, so nothing to say.
|
|
continue
|
|
}
|
|
if matchesAny(event, known) {
|
|
continue
|
|
}
|
|
out = append(out, fmt.Sprintf(
|
|
"%s consumes %q and %s emits %s — so that subscription would match nothing, and "+
|
|
"nothing would report it",
|
|
c.Module, pattern, emitter, listOf(known)))
|
|
}
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
// matchesAny says whether one of an emitter's event names satisfies a consumer's pattern.
|
|
func matchesAny(pattern string, emitted map[string]bool) bool {
|
|
want := strings.Split(pattern, ".")
|
|
for name := range emitted {
|
|
if matches(want, strings.Split(name, ".")) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func matches(pattern, name []string) bool {
|
|
for i, part := range pattern {
|
|
if part == catalogueTheRest {
|
|
return i < len(name)
|
|
}
|
|
if i >= len(name) {
|
|
return false
|
|
}
|
|
if part != "*" && part != name[i] {
|
|
return false
|
|
}
|
|
}
|
|
return len(pattern) == len(name)
|
|
}
|
|
|
|
func listOf(names map[string]bool) string {
|
|
if len(names) == 0 {
|
|
return "nothing"
|
|
}
|
|
out := make([]string, 0, len(names))
|
|
for n := range names {
|
|
out = append(out, n)
|
|
}
|
|
sort.Strings(out)
|
|
return strings.Join(out, ", ")
|
|
}
|