Files
mesh-delivery/sdk/heard/heard.go
T

116 lines
4.7 KiB
Go

// Package heard is the finding a part of mesh-delivery writes while one of its consumers or watches does not hear the
// family it follows (novox/hq ADR 0615 §5; review round 1 of the registry milestone): the consumer not there yet, a
// hearing that could not start, a watch that cannot be opened. Without it the part's health stays healthy and its
// status says answered while the events it waits on are lost to it. The finding names the observer and the family,
// says what is not heard and why, and is deleted when the consumer binds: at the part's next start, or where the part
// binds again at a change of its membership, then; the condition keeper raises and clears the condition from it (ADR 0564 §2).
package heard
import (
"encoding/json"
"fmt"
"strings"
"time"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
"git.novox.be/novox/mesh-sdk/go/views"
)
// Kind is the finding's kind.
const Kind = "family-not-heard"
// ID is the finding's subject: `<observer>.<family>`, the family by the name the part follows it under (a stream, an
// event, a state's watch).
func ID(observer, family string) string { return observer + "." + family }
// Key is the finding's key in the observer's findings state.
func Key(observer, family string) string {
return meshconditionsclient.Key(meshconditionsclient.ScopeModule, ID(observer, family), Kind)
}
// Words are the finding's words: its summary, headline and explanation. consequence says what the observer misses while
// it does not hear family ("the placement is the first view's alone").
func Words(observer, family, consequence string, why error) (summary, headline, explanation string) {
reason := "it gave no reason"
if why != nil {
reason = strings.TrimSpace(why.Error())
}
summary = fmt.Sprintf("%s does not hear %s: %s (%s)", observer, family, consequence, reason)
headline = fmt.Sprintf("%s does not hear %s", observer, family)
explanation = fmt.Sprintf("%s follows %s on a consumer of its own, and that consumer is not there or could not be heard, so %s. "+
"This clears when the consumer binds: at %s's next start, or sooner where %s binds again at a change of its membership. "+
"Look at whether the grants part made %s's consumers, then start %s again if it does not bind by itself.", observer, family, consequence, observer, observer, observer, observer)
return summary, headline, explanation
}
// Finding is observer's finding that it does not hear family, in the layout of the seat mesh-conditions' state findings.
func Finding(observer, family, consequence string, why error, at time.Time) (string, []byte, error) {
summary, headline, explanation := Words(observer, family, consequence, why)
v := meshconditionsclient.FindingFor(views.Finding{Observer: observer, Kind: Kind, ID: ID(observer, family), Summary: summary,
Headline: headline, Explanation: explanation, At: at})
if err := meshconditionsclient.CheckFinding(v); err != nil {
return v.Key, nil, err
}
value, err := json.Marshal(v)
return v.Key, value, err
}
// Writer writes and deletes the finding in the observer's findings state. Put or Delete nil writes nothing, said in the
// log, so the part still says it.
type Writer struct {
Observer string
Put func(key string, value []byte) error
Delete func(key string) error
Logf func(string, ...any)
Now func() time.Time
}
// Bind binds the observer's consumer of family (bind makes it and starts hearing it): the finding is deleted when it
// binds, and written, with why, when it does not. It answers bind's error, so a caller that cannot go on without the
// family still stops.
func (w Writer) Bind(family, consequence string, bind func() error) error {
if err := bind(); err != nil {
w.NotHeard(family, consequence, err)
return err
}
w.Heard(family)
return nil
}
// NotHeard says in the log and in the finding that the observer does not hear family.
func (w Writer) NotHeard(family, consequence string, why error) {
now := time.Now
if w.Now != nil {
now = w.Now
}
summary, _, _ := Words(w.Observer, family, consequence, why)
w.logf("%s", summary)
if w.Put == nil {
w.logf("the finding %s is not written: no findings state", Key(w.Observer, family))
return
}
key, value, err := Finding(w.Observer, family, consequence, why, now())
if err == nil {
err = w.Put(key, value)
}
if err != nil {
w.logf("the finding %s is not written: %v", key, err)
}
}
// Heard deletes the finding: the consumer of family is bound.
func (w Writer) Heard(family string) {
if w.Delete == nil {
return
}
if err := w.Delete(Key(w.Observer, family)); err != nil {
w.logf("the finding %s is not deleted: %v", Key(w.Observer, family), err)
}
}
func (w Writer) logf(format string, args ...any) {
if w.Logf != nil {
w.Logf(format, args...)
}
}