package broker 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, ", ") }