A setting reaches only what declares it, the mesh places its own files, registration refuses a name (issues 173, 174, ADR 0155) #175
@@ -91,19 +91,40 @@ func orNoSettings(layers []Layer) string {
|
|||||||
return "; set today: " + strings.Join(keys, ", ")
|
return "; set today: " + strings.Join(keys, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// settingKeysUsedBy is every key a module's files ask for, so a setting that lands in one is not
|
// settingKeysUsedBy is every key a module's files, contributions and served facts ask for, so a
|
||||||
// called stray.
|
// setting that lands in one is not called stray.
|
||||||
func settingKeysUsedBy(m Manifest) map[string]bool {
|
func settingKeysUsedBy(m Manifest) map[string]bool {
|
||||||
used := map[string]bool{}
|
used := map[string]bool{}
|
||||||
|
note := func(s string) {
|
||||||
|
for _, k := range settingsUsed(s) {
|
||||||
|
used[k] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, r := range m.Resources {
|
for _, r := range m.Resources {
|
||||||
if fmt.Sprint(r["type"]) != "file" {
|
if fmt.Sprint(r["type"]) != "file" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if content, ok := r["content"].(string); ok {
|
if content, ok := r["content"].(string); ok {
|
||||||
for _, k := range settingsUsed(content) {
|
note(content)
|
||||||
used[k] = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
inValues := func(values map[string]any) {
|
||||||
|
for _, v := range values {
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
note(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, values := range m.Contributes {
|
||||||
|
inValues(values)
|
||||||
|
}
|
||||||
|
for _, locals := range m.ContributesMany {
|
||||||
|
for _, values := range locals {
|
||||||
|
inValues(values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, values := range m.Serves {
|
||||||
|
inValues(values)
|
||||||
}
|
}
|
||||||
return used
|
return used
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ func Settle(base map[string]any, layers []Layer) (map[string]any, error) {
|
|||||||
// overridden lays settings over a map whose keys are its contract: a contribution, a served fact.
|
// overridden lays settings over a map whose keys are its contract: a contribution, a served fact.
|
||||||
// Only the keys the map already declares are touched; the rest of a layer is somebody else's
|
// Only the keys the map already declares are touched; the rest of a layer is somebody else's
|
||||||
// business (a file's, another destination's) and is left to reach it there.
|
// business (a file's, another destination's) and is left to reach it there.
|
||||||
|
//
|
||||||
|
// A declared value may itself be the operator's, `${setting:<key>}` (ADR 0155): a mail provider
|
||||||
|
// serves its domain, an identity provider its issuer, and neither is the definition's to state.
|
||||||
|
// Filled from the layers after the overrides, and refused by name when nothing sets it — a literal
|
||||||
|
// placeholder handed to a consumer is a service configured against a string nobody meant.
|
||||||
func overridden(base map[string]any, layers []Layer, what string) (map[string]any, error) {
|
func overridden(base map[string]any, layers []Layer, what string) (map[string]any, error) {
|
||||||
kept := make([]Layer, 0, len(layers))
|
kept := make([]Layer, 0, len(layers))
|
||||||
for _, layer := range layers {
|
for _, layer := range layers {
|
||||||
@@ -110,7 +115,28 @@ func overridden(base map[string]any, layers []Layer, what string) (map[string]an
|
|||||||
}
|
}
|
||||||
kept = append(kept, Layer{From: layer.From, Values: values})
|
kept = append(kept, Layer{From: layer.From, Values: values})
|
||||||
}
|
}
|
||||||
return settle(base, kept, nil, what)
|
merged, err := settle(base, kept, nil, what)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for key, value := range merged {
|
||||||
|
s, ok := value.(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, asked := range settingsUsed(s) {
|
||||||
|
v, set := settingValue(layers, asked)
|
||||||
|
if !set {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"%s says ${setting:%s} for %q, and nothing sets %q — an operator's value is the "+
|
||||||
|
"assignment's, never the definition's (novox/hq ADR 0112)%s",
|
||||||
|
what, asked, key, asked, orNoSettings(layers))
|
||||||
|
}
|
||||||
|
s = strings.ReplaceAll(s, "${setting:"+asked+"}", plainly(v))
|
||||||
|
}
|
||||||
|
merged[key] = s
|
||||||
|
}
|
||||||
|
return merged, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// settle lays the layers over a module's own values, in order.
|
// settle lays the layers over a module's own values, in order.
|
||||||
|
|||||||
@@ -213,3 +213,24 @@ func TestContentThatIsNotJSONIsRefusedWhereSomebodyIsLooking(t *testing.T) {
|
|||||||
t.Fatal("a module claiming to merge as JSON shipped something else and was accepted")
|
t.Fatal("a module claiming to merge as JSON shipped something else and was accepted")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAServedValueMayBeTheOperators(t *testing.T) {
|
||||||
|
// A mail provider serves its domain and an identity provider its issuer; neither is the
|
||||||
|
// definition's to state (ADR 0155). Filled from the layers, refused by name when unset.
|
||||||
|
served, err := Settle(map[string]any{"port": 587, "domain": "${setting:domain}"},
|
||||||
|
[]Layer{{From: "the mesh", Values: map[string]any{"domain": "example.tld"}}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if served["domain"] != "example.tld" {
|
||||||
|
t.Errorf("the operator's value did not fill the served key: %v", served)
|
||||||
|
}
|
||||||
|
_, err = Settle(map[string]any{"domain": "${setting:domain}"}, nil)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), `"domain"`) {
|
||||||
|
t.Errorf("a served value nothing sets must be refused by name; got %v", err)
|
||||||
|
}
|
||||||
|
m := Manifest{Module: "mail", Serves: map[string]map[string]any{"smtp": {"domain": "${setting:domain}"}}}
|
||||||
|
if unused := UnusedSettings(m, []Layer{{From: "the mesh", Values: map[string]any{"domain": "x"}}}); len(unused) != 0 {
|
||||||
|
t.Errorf("a setting a served fact asks for is not stray: %v", unused)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user