config: Fix merge of config with map[string]string values.

Fixes #8679
This commit is contained in:
Bjørn Erik Pedersen 2021-06-22 09:53:37 +02:00
parent 9312059888
commit 4a9d408fe0
9 changed files with 133 additions and 17 deletions

View file

@ -49,6 +49,15 @@ func ToParamsAndPrepare(in interface{}) (Params, bool) {
return m, true return m, true
} }
// MustToParamsAndPrepare calls ToParamsAndPrepare and panics if it fails.
func MustToParamsAndPrepare(in interface{}) Params {
if p, ok := ToParamsAndPrepare(in); ok {
return p
} else {
panic(fmt.Sprintf("cannot convert %T to maps.Params", in))
}
}
// ToStringMap converts in to map[string]interface{}. // ToStringMap converts in to map[string]interface{}.
func ToStringMap(in interface{}) map[string]interface{} { func ToStringMap(in interface{}) map[string]interface{} {
m, _ := ToStringMapE(in) m, _ := ToStringMapE(in)

View file

@ -21,10 +21,10 @@ import (
qt "github.com/frankban/quicktest" qt "github.com/frankban/quicktest"
) )
func TestToLower(t *testing.T) { func TestPrepareParams(t *testing.T) {
tests := []struct { tests := []struct {
input map[string]interface{} input Params
expected map[string]interface{} expected Params
}{ }{
{ {
map[string]interface{}{ map[string]interface{}{
@ -47,6 +47,9 @@ func TestToLower(t *testing.T) {
"gHi": map[string]interface{}{ "gHi": map[string]interface{}{
"J": 25, "J": 25,
}, },
"jKl": map[string]string{
"M": "26",
},
}, },
Params{ Params{
"abc": 32, "abc": 32,
@ -60,13 +63,16 @@ func TestToLower(t *testing.T) {
"ghi": Params{ "ghi": Params{
"j": 25, "j": 25,
}, },
"jkl": Params{
"m": "26",
},
}, },
}, },
} }
for i, test := range tests { for i, test := range tests {
t.Run(fmt.Sprint(i), func(t *testing.T) { t.Run(fmt.Sprint(i), func(t *testing.T) {
// ToLower modifies input. // PrepareParams modifies input.
PrepareParams(test.input) PrepareParams(test.input)
if !reflect.DeepEqual(test.expected, test.input) { if !reflect.DeepEqual(test.expected, test.input) {
t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input) t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input)

View file

@ -226,7 +226,7 @@ func toMergeStrategy(v interface{}) ParamsMergeStrategy {
// PrepareParams // PrepareParams
// * makes all the keys in the given map lower cased and will do so // * makes all the keys in the given map lower cased and will do so
// * This will modify the map given. // * This will modify the map given.
// * Any nested map[interface{}]interface{} will be converted to Params. // * Any nested map[interface{}]interface{}, map[string]interface{},map[string]string will be converted to Params.
// * Any _merge value will be converted to proper type and value. // * Any _merge value will be converted to proper type and value.
func PrepareParams(m Params) { func PrepareParams(m Params) {
for k, v := range m { for k, v := range m {
@ -236,7 +236,7 @@ func PrepareParams(m Params) {
v = toMergeStrategy(v) v = toMergeStrategy(v)
retyped = true retyped = true
} else { } else {
switch v.(type) { switch vv := v.(type) {
case map[interface{}]interface{}: case map[interface{}]interface{}:
var p Params = cast.ToStringMap(v) var p Params = cast.ToStringMap(v)
v = p v = p
@ -247,6 +247,14 @@ func PrepareParams(m Params) {
v = p v = p
PrepareParams(p) PrepareParams(p)
retyped = true retyped = true
case map[string]string:
p := make(Params)
for k, v := range vv {
p[k] = v
}
v = p
PrepareParams(p)
retyped = true
} }
} }

View file

@ -104,6 +104,10 @@ func (c *compositeConfig) Set(key string, value interface{}) {
c.layer.Set(key, value) c.layer.Set(key, value)
} }
func (c *compositeConfig) SetDefaults(params maps.Params) {
c.layer.SetDefaults(params)
}
func (c *compositeConfig) WalkParams(walkFn func(params ...KeyParams) bool) { func (c *compositeConfig) WalkParams(walkFn func(params ...KeyParams) bool) {
panic("not supported") panic("not supported")
} }

View file

@ -30,6 +30,7 @@ type Provider interface {
Get(key string) interface{} Get(key string) interface{}
Set(key string, value interface{}) Set(key string, value interface{})
Merge(key string, value interface{}) Merge(key string, value interface{})
SetDefaults(params maps.Params)
SetDefaultMergeStrategy() SetDefaultMergeStrategy()
WalkParams(walkFn func(params ...KeyParams) bool) WalkParams(walkFn func(params ...KeyParams) bool)
IsSet(key string) bool IsSet(key string) bool

View file

@ -163,10 +163,9 @@ func (c *defaultConfigProvider) Set(k string, v interface{}) {
} }
switch vv := v.(type) { switch vv := v.(type) {
case map[string]interface{}: case map[string]interface{}, map[interface{}]interface{}, map[string]string:
var p maps.Params = vv p := maps.MustToParamsAndPrepare(vv)
v = p v = p
maps.PrepareParams(p)
} }
key, m := c.getNestedKeyAndMap(k, true) key, m := c.getNestedKeyAndMap(k, true)
@ -183,6 +182,16 @@ func (c *defaultConfigProvider) Set(k string, v interface{}) {
m[key] = v m[key] = v
} }
// SetDefaults will set values from params if not already set.
func (c *defaultConfigProvider) SetDefaults(params maps.Params) {
maps.PrepareParams(params)
for k, v := range params {
if _, found := c.root[k]; !found {
c.root[k] = v
}
}
}
func (c *defaultConfigProvider) Merge(k string, v interface{}) { func (c *defaultConfigProvider) Merge(k string, v interface{}) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
@ -226,10 +235,9 @@ func (c *defaultConfigProvider) Merge(k string, v interface{}) {
} }
switch vv := v.(type) { switch vv := v.(type) {
case map[string]interface{}: case map[string]interface{}, map[interface{}]interface{}, map[string]string:
var p maps.Params = vv p := maps.MustToParamsAndPrepare(vv)
v = p v = p
maps.PrepareParams(p)
} }
key, m := c.getNestedKeyAndMap(k, true) key, m := c.getNestedKeyAndMap(k, true)

View file

@ -204,6 +204,85 @@ func TestDefaultConfigProvider(t *testing.T) {
}) })
}) })
// Issue #8679
c.Run("Merge typed maps", func(c *qt.C) {
for _, left := range []interface{}{
map[string]string{
"c": "cv1",
},
map[string]interface{}{
"c": "cv1",
},
map[interface{}]interface{}{
"c": "cv1",
},
} {
cfg := New()
cfg.Set("", map[string]interface{}{
"b": left,
})
cfg.Merge("", maps.Params{
"b": maps.Params{
"c": "cv2",
"d": "dv2",
},
})
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
"b": maps.Params{
"c": "cv1",
"d": "dv2",
},
})
}
for _, left := range []interface{}{
map[string]string{
"b": "bv1",
},
map[string]interface{}{
"b": "bv1",
},
map[interface{}]interface{}{
"b": "bv1",
},
} {
for _, right := range []interface{}{
map[string]string{
"b": "bv2",
"c": "cv2",
},
map[string]interface{}{
"b": "bv2",
"c": "cv2",
},
map[interface{}]interface{}{
"b": "bv2",
"c": "cv2",
},
} {
cfg := New()
cfg.Set("a", left)
cfg.Merge("a", right)
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
"a": maps.Params{
"b": "bv1",
"c": "cv2",
},
})
}
}
})
c.Run("IsSet", func(c *qt.C) { c.Run("IsSet", func(c *qt.C) {
cfg := New() cfg := New()

View file

@ -64,10 +64,6 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid
l := configLoader{ConfigSourceDescriptor: d, cfg: config.New()} l := configLoader{ConfigSourceDescriptor: d, cfg: config.New()}
if err := l.applyConfigDefaults(); err != nil {
return l.cfg, configFiles, err
}
for _, name := range d.configFilenames() { for _, name := range d.configFilenames() {
var filename string var filename string
filename, err := l.loadConfig(name) filename, err := l.loadConfig(name)
@ -78,6 +74,10 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid
} }
} }
if err := l.applyConfigDefaults(); err != nil {
return l.cfg, configFiles, err
}
if d.AbsConfigDir != "" { if d.AbsConfigDir != "" {
dcfg, dirnames, err := config.LoadConfigFromDir(l.Fs, d.AbsConfigDir, l.Environment) dcfg, dirnames, err := config.LoadConfigFromDir(l.Fs, d.AbsConfigDir, l.Environment)
if err == nil { if err == nil {
@ -298,7 +298,7 @@ func (l configLoader) applyConfigDefaults() error {
"enableInlineShortcodes": false, "enableInlineShortcodes": false,
} }
l.cfg.Merge("", defaultSettings) l.cfg.SetDefaults(defaultSettings)
return nil return nil
} }

View file

@ -56,6 +56,7 @@ title: P1
b.AssertFileContent("public/p1/index.html", `Link First Link|PARTIAL1_EDITED PARTIAL2_EDITEDEND`) b.AssertFileContent("public/p1/index.html", `Link First Link|PARTIAL1_EDITED PARTIAL2_EDITEDEND`)
} }
func TestRenderHooks(t *testing.T) { func TestRenderHooks(t *testing.T) {
config := ` config := `
baseURL="https://example.org" baseURL="https://example.org"