mirror of
https://github.com/spf13/viper
synced 2025-04-28 08:17:17 +00:00
Merge e01b6d7d0e
into 1508a7ba44
This commit is contained in:
commit
d11991527c
2 changed files with 172 additions and 0 deletions
91
viper.go
91
viper.go
|
@ -498,6 +498,92 @@ func (v *Viper) searchMap(source map[string]any, path []string) any {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// searchMapWithAliases recursively searches for slice field in source map and
|
||||||
|
// replace them with the environment variable value if it exists.
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
if len(sourceValue) <= 0 {
|
||||||
|
return newSliceValues, false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var exists []bool
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
envKey := envKey + v.keyDelim + strconv.Itoa(i)
|
||||||
|
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, 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 {
|
||||||
|
exists = append(exists, false || existDefault)
|
||||||
|
if existDefault {
|
||||||
|
newSliceValues = append(newSliceValues, existingValue)
|
||||||
|
} else {
|
||||||
|
return newSliceValues, slices.Contains(exists, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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, 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, slices.Contains(exists, true)
|
||||||
|
|
||||||
|
default:
|
||||||
|
if newVal, ok := v.getEnv(v.mergeWithEnvPrefix(envKey)); ok {
|
||||||
|
return newVal, true
|
||||||
|
} else {
|
||||||
|
return source, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// searchIndexableWithPathPrefixes recursively searches for a value for path in source map/slice.
|
// searchIndexableWithPathPrefixes recursively searches for a value for path in source map/slice.
|
||||||
//
|
//
|
||||||
// While searchMap() considers each path element as a single map key or slice index, this
|
// While searchMap() considers each path element as a single map key or slice index, this
|
||||||
|
@ -715,6 +801,11 @@ func (v *Viper) Get(key string) any {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for Env override again, to handle slices
|
||||||
|
if v.automaticEnvApplied {
|
||||||
|
val, _ = v.searchAndReplaceSliceValueWithEnv(val, lcaseKey)
|
||||||
|
}
|
||||||
|
|
||||||
if v.typeByDefValue {
|
if v.typeByDefValue {
|
||||||
// TODO(bep) this branch isn't covered by a single test.
|
// TODO(bep) this branch isn't covered by a single test.
|
||||||
valType := val
|
valType := val
|
||||||
|
|
|
@ -2573,6 +2573,87 @@ func TestSliceIndexAccess(t *testing.T) {
|
||||||
assert.Equal(t, "Static", v.GetString("tv.0.episodes.1.2"))
|
assert.Equal(t, "Static", v.GetString("tv.0.episodes.1.2"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var yamlSimpleSlice = []byte(`
|
||||||
|
name: Steve
|
||||||
|
port: 8080
|
||||||
|
auth:
|
||||||
|
secret: 88888-88888
|
||||||
|
modes:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- 3
|
||||||
|
clients:
|
||||||
|
- name: foo
|
||||||
|
- name: bar
|
||||||
|
proxy:
|
||||||
|
clients:
|
||||||
|
- name: proxy_foo
|
||||||
|
- name: proxy_bar
|
||||||
|
- name: proxy_baz
|
||||||
|
`)
|
||||||
|
|
||||||
|
func TestSliceIndexAutomaticEnv(t *testing.T) {
|
||||||
|
v.SetConfigType("yaml")
|
||||||
|
r := strings.NewReader(string(yamlSimpleSlice))
|
||||||
|
|
||||||
|
type ClientConfig struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthConfig struct {
|
||||||
|
Secret string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxyConfig struct {
|
||||||
|
Clients []ClientConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type Configuration struct {
|
||||||
|
Port int
|
||||||
|
Name string
|
||||||
|
Auth AuthConfig
|
||||||
|
Modes []int
|
||||||
|
Clients []ClientConfig
|
||||||
|
Proxy ProxyConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read yaml as default value
|
||||||
|
err := v.unmarshalReader(r, v.config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "Steve", v.GetString("name"))
|
||||||
|
assert.Equal(t, 8080, v.GetInt("port"))
|
||||||
|
assert.Equal(t, "88888-88888", v.GetString("auth.secret"))
|
||||||
|
assert.Equal(t, "foo", v.GetString("clients.0.name"))
|
||||||
|
assert.Equal(t, "bar", v.GetString("clients.1.name"))
|
||||||
|
assert.Equal(t, "proxy_foo", v.GetString("proxy.clients.0.name"))
|
||||||
|
assert.Equal(t, []int{1, 2, 3}, v.GetIntSlice("modes"))
|
||||||
|
|
||||||
|
// Override with env variable
|
||||||
|
t.Setenv("NAME", "Steven")
|
||||||
|
t.Setenv("AUTH_SECRET", "99999-99999")
|
||||||
|
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()
|
||||||
|
|
||||||
|
// Unmarshal into struct
|
||||||
|
var config Configuration
|
||||||
|
v.Unmarshal(&config)
|
||||||
|
|
||||||
|
assert.Equal(t, "Steven", config.Name)
|
||||||
|
assert.Equal(t, 8080, config.Port)
|
||||||
|
assert.Equal(t, "99999-99999", config.Auth.Secret)
|
||||||
|
assert.Equal(t, []int{1, 2, 300}, config.Modes)
|
||||||
|
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) {
|
func TestIsPathShadowedInFlatMap(t *testing.T) {
|
||||||
v := New()
|
v := New()
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue