mirror of
https://github.com/spf13/viper
synced 2024-11-16 18:07:02 +00:00
AllKeys() should include slices allowing overriding of keys with slices with environment variables
This commit is contained in:
parent
a7cfd8b8e0
commit
29dcff61f2
2 changed files with 83 additions and 5 deletions
54
viper.go
54
viper.go
|
@ -1035,8 +1035,8 @@ func (v *Viper) Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error
|
||||||
return decode(v.AllSettings(), defaultDecoderConfig(rawVal, opts...))
|
return decode(v.AllSettings(), defaultDecoderConfig(rawVal, opts...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultDecoderConfig returns default mapsstructure.DecoderConfig with suppot
|
// defaultDecoderConfig returns a default mapstructure.DecoderConfig with support
|
||||||
// of time.Duration values & string slices
|
// for time.Duration values & string slices
|
||||||
func defaultDecoderConfig(output interface{}, opts ...DecoderConfigOption) *mapstructure.DecoderConfig {
|
func defaultDecoderConfig(output interface{}, opts ...DecoderConfigOption) *mapstructure.DecoderConfig {
|
||||||
c := &mapstructure.DecoderConfig{
|
c := &mapstructure.DecoderConfig{
|
||||||
Metadata: nil,
|
Metadata: nil,
|
||||||
|
@ -1976,6 +1976,8 @@ func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]interfac
|
||||||
m2 = val.(map[string]interface{})
|
m2 = val.(map[string]interface{})
|
||||||
case map[interface{}]interface{}:
|
case map[interface{}]interface{}:
|
||||||
m2 = cast.ToStringMap(val)
|
m2 = cast.ToStringMap(val)
|
||||||
|
case []interface{}:
|
||||||
|
m2 = castSliceToStringMap(val.([]interface{}))
|
||||||
default:
|
default:
|
||||||
// immediate value
|
// immediate value
|
||||||
shadow[strings.ToLower(fullKey)] = true
|
shadow[strings.ToLower(fullKey)] = true
|
||||||
|
@ -1987,6 +1989,17 @@ func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]interfac
|
||||||
return shadow
|
return shadow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// castSliceToStringMap converts a slice to a map where the keys are the indices.
|
||||||
|
func castSliceToStringMap(v []interface{}) map[string]interface{} {
|
||||||
|
m := make(map[string]interface{}, len(v))
|
||||||
|
|
||||||
|
for i, val := range v {
|
||||||
|
m[strconv.Itoa(i)] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
// mergeFlatMap merges the given maps, excluding values of the second map
|
// mergeFlatMap merges the given maps, excluding values of the second map
|
||||||
// shadowed by values from the first map.
|
// shadowed by values from the first map.
|
||||||
func (v *Viper) mergeFlatMap(shadow map[string]bool, m map[string]interface{}) map[string]bool {
|
func (v *Viper) mergeFlatMap(shadow map[string]bool, m map[string]interface{}) map[string]bool {
|
||||||
|
@ -2028,9 +2041,46 @@ func (v *Viper) AllSettings() map[string]interface{} {
|
||||||
// set innermost value
|
// set innermost value
|
||||||
deepestMap[lastKey] = value
|
deepestMap[lastKey] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convert any maps of integer keys back to slices
|
||||||
|
i := convertMapsToSlices(m)
|
||||||
|
if m2, ok := i.(map[string]interface{}); ok {
|
||||||
|
m = m2
|
||||||
|
}
|
||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convertMapsToSlices will do a deep check for any maps where the keys are all integers (as strings)
|
||||||
|
// as well as being contiguous from 0, and convert them to a slice.
|
||||||
|
func convertMapsToSlices(m map[string]interface{}) interface{} {
|
||||||
|
allInts := true
|
||||||
|
for k, v := range m {
|
||||||
|
if _, ok := strconv.Atoi(k); ok != nil {
|
||||||
|
allInts = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if m2, ok := v.(map[string]interface{}); ok {
|
||||||
|
m[k] = convertMapsToSlices(m2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allInts {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
s := make([]interface{}, len(m))
|
||||||
|
for i := 0; i < len(m); i++ {
|
||||||
|
v, ok := m[strconv.Itoa(i)]
|
||||||
|
if !ok {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
s[i] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// SetFs sets the filesystem to use to read configuration.
|
// SetFs sets the filesystem to use to read configuration.
|
||||||
func SetFs(fs afero.Fs) { v.SetFs(fs) }
|
func SetFs(fs afero.Fs) { v.SetFs(fs) }
|
||||||
|
|
||||||
|
|
|
@ -570,6 +570,29 @@ func TestAutoEnv(t *testing.T) {
|
||||||
assert.Equal(t, "13", Get("foo_bar"))
|
assert.Equal(t, "13", Get("foo_bar"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAutoEnvWithSlice(t *testing.T) {
|
||||||
|
initJSON()
|
||||||
|
|
||||||
|
AutomaticEnv()
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
Batters struct {
|
||||||
|
Batter []struct {
|
||||||
|
Type string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testutil.Setenv(t, "BATTERS.BATTER.1.TYPE", "Small")
|
||||||
|
|
||||||
|
var C config
|
||||||
|
err := Unmarshal(&C)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to decode into struct, %v", err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, []struct{ Type string }{{"Regular"}, {"Small"}, {"Blueberry"}, {"Devil's Food"}}, C.Batters.Batter)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAutoEnvWithPrefix(t *testing.T) {
|
func TestAutoEnvWithPrefix(t *testing.T) {
|
||||||
Reset()
|
Reset()
|
||||||
|
|
||||||
|
@ -620,8 +643,13 @@ func TestAllKeys(t *testing.T) {
|
||||||
"name",
|
"name",
|
||||||
"beard",
|
"beard",
|
||||||
"ppu",
|
"ppu",
|
||||||
"batters.batter",
|
"batters.batter.0.type",
|
||||||
"hobbies",
|
"batters.batter.1.type",
|
||||||
|
"batters.batter.2.type",
|
||||||
|
"batters.batter.3.type",
|
||||||
|
"hobbies.0",
|
||||||
|
"hobbies.1",
|
||||||
|
"hobbies.2",
|
||||||
"clothing.jacket",
|
"clothing.jacket",
|
||||||
"clothing.trousers",
|
"clothing.trousers",
|
||||||
"default.import_path",
|
"default.import_path",
|
||||||
|
@ -1983,7 +2011,7 @@ clothing:
|
||||||
|
|
||||||
func TestDotParameter(t *testing.T) {
|
func TestDotParameter(t *testing.T) {
|
||||||
initJSON()
|
initJSON()
|
||||||
// shoud take precedence over batters defined in jsonExample
|
// should take precedence over batters defined in jsonExample
|
||||||
r := bytes.NewReader([]byte(`{ "batters.batter": [ { "type": "Small" } ] }`))
|
r := bytes.NewReader([]byte(`{ "batters.batter": [ { "type": "Small" } ] }`))
|
||||||
unmarshalReader(r, v.config)
|
unmarshalReader(r, v.config)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue