package catalogue import ( "fmt" "sort" "strings" ) // A preference has a default; the operator's own value has none (novox/hq ADR 0262, extending ADR // 0112 and taking the default half of ADR 0164). // // `${setting:}` was refused whenever no layer set the key, and `settings set` refused a key no // file asked for yet. Together they meant a running module could never gain a setting: the file that // asks for it fails to compose until somebody sets it, and nobody can set it until the file asks. // A definition may now give a key a default, with why, when the value is a **preference** — a font // size, a width, a number of workers: something true of the software that a machine may tune. A // value that is inherently the operator's — a domain, a public name, an identity, a secret — gets no // default and stays refused by name until a layer sets it, which is what ADR 0112 and ADR 0155 were // written for. // // The default is the lowest layer. The mesh's layer, then the node's, override it. It fills only // `${setting:}`: a mergeable file's content already is its defaults, and laying a default over // it would reach every mergeable file of the module (novox/hq issue 168). // SettingDeclaration is one key's default, as the definition gives it. type SettingDeclaration struct { // Kind says what sort of value this is. `preference` is the only kind with a default; it is // stated rather than assumed so a reviewer sees the claim being made. Kind string `json:"kind"` // Default is the value when no layer sets the key: a string, a number or a boolean. Default any `json:"default"` // Why this default: one sentence for whoever wonders whether to change it. Why string `json:"why"` } // KindPreference is the kind of a setting that may have a default. const KindPreference = "preference" // DefaultLayer is what the layer of a module's own defaults is called where a value's source is said. const DefaultLayer = "default" // meshWords are the settings keys the mesh reads itself; a module declares none of them. var meshWords = map[string]bool{ PortsSetting: true, ExposeSetting: true, ReachSetting: true, EndpointsSetting: true, PlacesSetting: true, AccessesSetting: true, NetworksSetting: true, } // operatorsOwn are words that, in a key's name, say its value is the operator's and never a // preference: a default for one would be the literal ADR 0112 removed from definitions. var operatorsOwn = []string{ "domain", "host", "issuer", "url", "email", "mail", "address", "identity", "login", "user", "account", "password", "secret", "token", "credential", } // SettingProblems is every way a definition's setting defaults are wrong, in its own words. func SettingProblems(m Manifest) []string { if len(m.Settings) == 0 { return nil } used := settingKeysUsedBy(m) var problems []string for _, key := range sortedSettingKeys(m.Settings) { d := m.Settings[key] say := func(format string, args ...any) { problems = append(problems, fmt.Sprintf("%s's setting %q: ", m.Module, key)+fmt.Sprintf(format, args...)) } if !settingRef.MatchString("${setting:" + key + "}") { say("not a usable key: lower-case letters, digits, dots, dashes and underscores") continue } if meshWords[key] { say("the mesh reads %q itself, and a module gives it no default", key) continue } if d.Kind != KindPreference { say("kind is %q, and only a %q has a default (novox/hq ADR 0262)", d.Kind, KindPreference) } for _, word := range operatorsOwn { if strings.Contains(key, word) { say("a key naming %q is the operator's value, and has no default — it is the "+ "assignment's, never the definition's (novox/hq ADR 0112, ADR 0262)", word) break } } switch v := d.Default.(type) { case string: if strings.TrimSpace(v) == "" { say("an empty default is no default; give the value, or declare nothing") } case float64, bool: case nil: say("no default: a key without one is the operator's, and is not declared here") default: say("a default is a string, a number or a boolean, and this is %T", d.Default) } if strings.TrimSpace(d.Why) == "" { say("no why: say in one sentence why this default") } if !used[key] { say("nothing asks for ${setting:%s}, so the default reaches nothing", key) } } return problems } // Defaults is the layer a module's own defaults make, or nothing when it gives none. func Defaults(m Manifest) (Layer, bool) { if len(m.Settings) == 0 { return Layer{}, false } values := map[string]any{} for key, d := range m.Settings { if d.Default != nil { values[key] = d.Default } } return Layer{From: DefaultLayer, Values: values}, len(values) > 0 } // WithDefaults is a module's layers with its defaults under them, for filling `${setting:}`. func WithDefaults(m Manifest, layers []Layer) []Layer { d, has := Defaults(m) if !has { return layers } return append([]Layer{d}, layers...) } // SettingSource is one key's effective value and the layer it came from. type SettingSource struct { Key string Value any // From is DefaultLayer, MeshWideLayer or the node's name. From string // Default is the module's default, when it gives one. Default any HasDefault bool } // Effective is every key a module gives a default or a layer sets, with its value and where it came // from: the default, then the mesh's layer, then the node's — later wins. func Effective(m Manifest, layers []Layer) []SettingSource { byKey := map[string]*SettingSource{} for key, d := range m.Settings { byKey[key] = &SettingSource{Key: key, Value: d.Default, From: DefaultLayer, Default: d.Default, HasDefault: true} } for _, layer := range layers { for key, v := range layer.Values { s, ok := byKey[key] if !ok { s = &SettingSource{Key: key} byKey[key] = s } s.Value, s.From = v, layer.From } } out := make([]SettingSource, 0, len(byKey)) for _, s := range byKey { out = append(out, *s) } sort.Slice(out, func(i, j int) bool { return out[i].Key < out[j].Key }) return out } func sortedSettingKeys(in map[string]SettingDeclaration) []string { keys := make([]string, 0, len(in)) for k := range in { keys = append(keys, k) } sort.Strings(keys) return keys }