From a60b38b333eb5118cd86521283816a9863e6f8d3 Mon Sep 17 00:00:00 2001 From: Darrian <3525311+rikkuness@users.noreply.github.com> Date: Wed, 27 Apr 2022 14:46:38 +0100 Subject: [PATCH] correct GetStringMap inconsistency, fixes #708 --- viper.go | 29 ++++++++++++++++++++++++++--- viper_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/viper.go b/viper.go index 4a9dac9..a9edc72 100644 --- a/viper.go +++ b/viper.go @@ -1041,21 +1041,21 @@ func (v *Viper) GetStringSlice(key string) []string { func GetStringMap(key string) map[string]interface{} { return v.GetStringMap(key) } func (v *Viper) GetStringMap(key string) map[string]interface{} { - return cast.ToStringMap(v.Get(key)) + return cast.ToStringMap(v.allSettingsUnderParent(key)) } // GetStringMapString returns the value associated with the key as a map of strings. func GetStringMapString(key string) map[string]string { return v.GetStringMapString(key) } func (v *Viper) GetStringMapString(key string) map[string]string { - return cast.ToStringMapString(v.Get(key)) + return cast.ToStringMapString(v.allSettingsUnderParent(key)) } // GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings. func GetStringMapStringSlice(key string) map[string][]string { return v.GetStringMapStringSlice(key) } func (v *Viper) GetStringMapStringSlice(key string) map[string][]string { - return cast.ToStringMapStringSlice(v.Get(key)) + return cast.ToStringMapStringSlice(v.allSettingsUnderParent(key)) } // GetSizeInBytes returns the size of the value associated with the given key @@ -2016,6 +2016,29 @@ func (v *Viper) AllSettings() map[string]interface{} { return m } +func (v *Viper) allSettingsUnderParent(parent string) map[string]interface{} { + m := map[string]interface{}{} + // start from the list of keys, and construct the map one value at a time + for _, k := range v.AllKeys() { + if !strings.HasPrefix(k, parent) { + continue + } + + value := v.Get(k) + if value == nil { + // should not happen, since AllKeys() returns only keys holding a value, + // check just in case anything changes + continue + } + path := strings.Split(strings.TrimPrefix(k, parent+v.keyDelim), v.keyDelim) + lastKey := strings.ToLower(path[len(path)-1]) + deepestMap := deepSearch(m, path[0:len(path)-1]) + // set innermost value + deepestMap[lastKey] = value + } + return m +} + // SetFs sets the filesystem to use to read configuration. func SetFs(fs afero.Fs) { v.SetFs(fs) } diff --git a/viper_test.go b/viper_test.go index c41a1e7..cccb1d4 100644 --- a/viper_test.go +++ b/viper_test.go @@ -878,6 +878,33 @@ func TestRecursiveAliases(t *testing.T) { RegisterAlias("Roo", "baz") } +func TestStringMapOverrides(t *testing.T) { + v := New() + + v.SetDefault("foo.bar", "default-value") + v.SetDefault("foo.baz", 1) + v.SetDefault("deeper.nest.a", "default-value") + v.SetDefault("deeper.nest.b", "default-value") + + v.SetEnvPrefix("test") + v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + v.AutomaticEnv() + + testutil.Setenv(t, "TEST_FOO_BAR", "overidden") + testutil.Setenv(t, "TEST_DEEPER_NEST_A", "overidden") + + assert.Equal(t, "overidden", v.GetString("foo.bar")) + assert.Equal(t, map[string]interface{}{ + "bar": "overidden", + "baz": 1, + }, v.GetStringMap("foo")) + + assert.Equal(t, map[string]string{ + "a": "overidden", + "b": "default-value", + }, v.GetStringMapString("deeper.nest")) +} + func TestUnmarshal(t *testing.T) { SetDefault("port", 1313) Set("name", "Steve")