diff --git a/viper.go b/viper.go index c223a90..79c987a 100644 --- a/viper.go +++ b/viper.go @@ -28,6 +28,7 @@ import ( "os" "path/filepath" "reflect" + "slices" "strconv" "strings" "sync" @@ -692,52 +693,85 @@ func (v *Viper) searchMap(source map[string]any, path []string) any { // searchMapWithAliases recursively searches for slice field in source map and // replace them with the environment variable value if it exists. // -// Returns replaced values. -func (v *Viper) searchAndReplaceSliceValueWithEnv(source any, envKey string) any { +// Returns replaced values, and a boolean if the value was found in +// environment varaible. +func (v *Viper) searchAndReplaceSliceValueWithEnv(source any, envKey string) (any, bool) { + switch sourceValue := source.(type) { case []any: var newSliceValues []any - for i, sliceValue := range sourceValue { + if len(sourceValue) <= 0 { + return newSliceValues, false + + } + + var exists []bool + for i := 0; ; i++ { envKey := envKey + v.keyDelim + strconv.Itoa(i) - switch existingValue := sliceValue.(type) { + var value any + var existDefault = true + if len(sourceValue) < i+1 { + value = sourceValue[0] + existDefault = false + } else { + value = sourceValue[i] + } + switch existingValue := value.(type) { case map[string]any: - newVal := v.searchAndReplaceSliceValueWithEnv(existingValue, envKey) + newVal, found := v.searchAndReplaceSliceValueWithEnv(existingValue, envKey) + if !found && !existDefault { + return newSliceValues, slices.Contains(exists, true) + } newSliceValues = append(newSliceValues, newVal) + exists = append(exists, found || existDefault) default: if newVal, ok := v.getEnv(v.mergeWithEnvPrefix(envKey)); ok { newSliceValues = append(newSliceValues, newVal) + exists = append(exists, true) } else { - newSliceValues = append(newSliceValues, existingValue) + exists = append(exists, false || existDefault) + if existDefault { + newSliceValues = append(newSliceValues, existingValue) + } else { + return newSliceValues, slices.Contains(exists, true) + } } } } - return newSliceValues + return newSliceValues, slices.Contains(exists, true) case map[string]any: var newMapValues map[string]any = make(map[string]any) + var exists []bool for key, mapValue := range sourceValue { envKey := envKey + v.keyDelim + key switch existingValue := mapValue.(type) { case map[string]any: - newVal := v.searchAndReplaceSliceValueWithEnv(existingValue, envKey) + newVal, found := v.searchAndReplaceSliceValueWithEnv(existingValue, envKey) + if !found { + return newMapValues, false + } newMapValues[key] = newVal + exists = append(exists, found) default: if newVal, ok := v.getEnv(v.mergeWithEnvPrefix(envKey)); ok { newMapValues[key] = newVal + exists = append(exists, true) } else { + exists = append(exists, false) newMapValues[key] = existingValue } } } - return newMapValues + return newMapValues, slices.Contains(exists, true) default: if newVal, ok := v.getEnv(v.mergeWithEnvPrefix(envKey)); ok { - return newVal + return newVal, true } else { - return source + return source, false } } } @@ -961,7 +995,7 @@ func (v *Viper) Get(key string) any { // Check for Env override again, to handle slices if v.automaticEnvApplied { - val = v.searchAndReplaceSliceValueWithEnv(val, lcaseKey) + val, _ = v.searchAndReplaceSliceValueWithEnv(val, lcaseKey) } if v.typeByDefValue { diff --git a/viper_test.go b/viper_test.go index cb95345..814967a 100644 --- a/viper_test.go +++ b/viper_test.go @@ -2668,6 +2668,7 @@ func TestSliceIndexAutomaticEnv(t *testing.T) { t.Setenv("MODES_2", "300") t.Setenv("CLIENTS_1_NAME", "baz") t.Setenv("PROXY_CLIENTS_0_NAME", "ProxyFoo") + t.Setenv("PROXY_CLIENTS_3_NAME", "ProxyNew") SetEnvKeyReplacer(strings.NewReplacer(".", "_")) AutomaticEnv() @@ -2683,6 +2684,7 @@ func TestSliceIndexAutomaticEnv(t *testing.T) { assert.Equal(t, "foo", config.Clients[0].Name) assert.Equal(t, "baz", config.Clients[1].Name) assert.Equal(t, "ProxyFoo", config.Proxy.Clients[0].Name) + assert.Equal(t, "ProxyNew", config.Proxy.Clients[3].Name) } func TestIsPathShadowedInFlatMap(t *testing.T) {