mirror of
https://github.com/spf13/viper
synced 2025-05-07 20:57:18 +00:00
Add viper.SetKeysCaseSensitive() to disable automatic key lowercasing.
This commit is contained in:
parent
b5bf975e58
commit
66b6dc1a03
3 changed files with 149 additions and 41 deletions
|
@ -62,7 +62,8 @@ item below it:
|
|||
* key/value store
|
||||
* default
|
||||
|
||||
Viper configuration keys are case insensitive.
|
||||
Viper configuration keys are case insensitive by default. They can be made case
|
||||
sensitive with `viper.SetKeyCaseSensitivity(true)`.
|
||||
|
||||
## Putting Values into Viper
|
||||
|
||||
|
|
116
viper.go
116
viper.go
|
@ -189,6 +189,7 @@ type Viper struct {
|
|||
automaticEnvApplied bool
|
||||
envKeyReplacer *strings.Replacer
|
||||
allowEmptyEnv bool
|
||||
caseSensitiveKeys bool
|
||||
|
||||
config map[string]interface{}
|
||||
override map[string]interface{}
|
||||
|
@ -491,7 +492,6 @@ func (v *Viper) providerPathExists(p *defaultRemoteProvider) bool {
|
|||
|
||||
// searchMap recursively searches for a value for path in source map.
|
||||
// Returns nil if not found.
|
||||
// Note: This assumes that the path entries and map keys are lower cased.
|
||||
func (v *Viper) searchMap(source map[string]interface{}, path []string) interface{} {
|
||||
if len(path) == 0 {
|
||||
return source
|
||||
|
@ -538,7 +538,7 @@ func (v *Viper) searchMapWithPathPrefixes(source map[string]interface{}, path []
|
|||
|
||||
// search for path prefixes, starting from the longest one
|
||||
for i := len(path); i > 0; i-- {
|
||||
prefixKey := strings.ToLower(strings.Join(path[0:i], v.keyDelim))
|
||||
prefixKey := v.caseKey(strings.Join(path[0:i], v.keyDelim))
|
||||
|
||||
next, ok := source[prefixKey]
|
||||
if ok {
|
||||
|
@ -659,7 +659,7 @@ func GetViper() *Viper {
|
|||
}
|
||||
|
||||
// Get can retrieve any value given the key to use.
|
||||
// Get is case-insensitive for a key.
|
||||
// Get's case-sensitivity for key is determined by viper.keyCaseSensitivity.
|
||||
// Get has the behavior of returning the value associated with the first
|
||||
// place from where it is set. Viper will check in the following order:
|
||||
// override, flag, env, config file, key/value store, default
|
||||
|
@ -667,8 +667,8 @@ func GetViper() *Viper {
|
|||
// Get returns an interface. For a specific value use one of the Get____ methods.
|
||||
func Get(key string) interface{} { return v.Get(key) }
|
||||
func (v *Viper) Get(key string) interface{} {
|
||||
lcaseKey := strings.ToLower(key)
|
||||
val := v.find(lcaseKey)
|
||||
casedKey := v.caseKey(key)
|
||||
val := v.find(casedKey)
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -676,7 +676,7 @@ func (v *Viper) Get(key string) interface{} {
|
|||
if v.typeByDefValue {
|
||||
// TODO(bep) this branch isn't covered by a single test.
|
||||
valType := val
|
||||
path := strings.Split(lcaseKey, v.keyDelim)
|
||||
path := strings.Split(casedKey, v.keyDelim)
|
||||
defVal := v.searchMap(v.defaults, path)
|
||||
if defVal != nil {
|
||||
valType = defVal
|
||||
|
@ -712,7 +712,7 @@ func (v *Viper) Get(key string) interface{} {
|
|||
}
|
||||
|
||||
// Sub returns new Viper instance representing a sub tree of this instance.
|
||||
// Sub is case-insensitive for a key.
|
||||
// Sub's case-sensitivity for key is determined by viper.keyCaseSensitivity.
|
||||
func Sub(key string) *Viper { return v.Sub(key) }
|
||||
func (v *Viper) Sub(key string) *Viper {
|
||||
subv := New()
|
||||
|
@ -938,7 +938,7 @@ func (v *Viper) BindFlagValue(key string, flag FlagValue) error {
|
|||
if flag == nil {
|
||||
return fmt.Errorf("flag for %q is nil", key)
|
||||
}
|
||||
v.pflags[strings.ToLower(key)] = flag
|
||||
v.pflags[v.caseKey(key)] = flag
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -953,7 +953,7 @@ func (v *Viper) BindEnv(input ...string) error {
|
|||
return fmt.Errorf("BindEnv missing key to bind to")
|
||||
}
|
||||
|
||||
key = strings.ToLower(input[0])
|
||||
key = v.caseKey(input[0])
|
||||
|
||||
if len(input) == 1 {
|
||||
envkey = v.mergeWithEnvPrefix(key)
|
||||
|
@ -970,13 +970,14 @@ func (v *Viper) BindEnv(input ...string) error {
|
|||
// Viper will check in the following order:
|
||||
// flag, env, config file, key/value store, default.
|
||||
// Viper will check to see if an alias exists first.
|
||||
// Note: this assumes a lower-cased key given.
|
||||
func (v *Viper) find(lcaseKey string) interface{} {
|
||||
// Note: By default, this assumes that a lowercase key is given.
|
||||
// This behavior can be modified with viper.SetKeysCaseSensitive.
|
||||
func (v *Viper) find(key string) interface{} {
|
||||
|
||||
var (
|
||||
val interface{}
|
||||
exists bool
|
||||
path = strings.Split(lcaseKey, v.keyDelim)
|
||||
path = strings.Split(key, v.keyDelim)
|
||||
nested = len(path) > 1
|
||||
)
|
||||
|
||||
|
@ -986,8 +987,8 @@ func (v *Viper) find(lcaseKey string) interface{} {
|
|||
}
|
||||
|
||||
// if the requested key is an alias, then return the proper key
|
||||
lcaseKey = v.realKey(lcaseKey)
|
||||
path = strings.Split(lcaseKey, v.keyDelim)
|
||||
key = v.realKey(key)
|
||||
path = strings.Split(key, v.keyDelim)
|
||||
nested = len(path) > 1
|
||||
|
||||
// Set() override first
|
||||
|
@ -1000,7 +1001,7 @@ func (v *Viper) find(lcaseKey string) interface{} {
|
|||
}
|
||||
|
||||
// PFlag override next
|
||||
flag, exists := v.pflags[lcaseKey]
|
||||
flag, exists := v.pflags[key]
|
||||
if exists && flag.HasChanged() {
|
||||
switch flag.ValueType() {
|
||||
case "int", "int8", "int16", "int32", "int64":
|
||||
|
@ -1024,14 +1025,14 @@ func (v *Viper) find(lcaseKey string) interface{} {
|
|||
if v.automaticEnvApplied {
|
||||
// even if it hasn't been registered, if automaticEnv is used,
|
||||
// check any Get request
|
||||
if val, ok := v.getEnv(v.mergeWithEnvPrefix(lcaseKey)); ok {
|
||||
if val, ok := v.getEnv(v.mergeWithEnvPrefix(key)); ok {
|
||||
return val
|
||||
}
|
||||
if nested && v.isPathShadowedInAutoEnv(path) != "" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
envkey, exists := v.env[lcaseKey]
|
||||
envkey, exists := v.env[key]
|
||||
if exists {
|
||||
if val, ok := v.getEnv(envkey); ok {
|
||||
return val
|
||||
|
@ -1070,7 +1071,7 @@ func (v *Viper) find(lcaseKey string) interface{} {
|
|||
|
||||
// last chance: if no other value is returned and a flag does exist for the value,
|
||||
// get the flag's value even if the flag's value has not changed
|
||||
if flag, exists := v.pflags[lcaseKey]; exists {
|
||||
if flag, exists := v.pflags[key]; exists {
|
||||
switch flag.ValueType() {
|
||||
case "int", "int8", "int16", "int32", "int64":
|
||||
return cast.ToInt(flag.ValueString())
|
||||
|
@ -1100,11 +1101,12 @@ func readAsCSV(val string) ([]string, error) {
|
|||
}
|
||||
|
||||
// IsSet checks to see if the key has been set in any of the data locations.
|
||||
// IsSet is case-insensitive for a key.
|
||||
// IsSet is case-insensitive for a key. This behavior can be modified
|
||||
// with viper.SetKeysCaseSensitive.
|
||||
func IsSet(key string) bool { return v.IsSet(key) }
|
||||
func (v *Viper) IsSet(key string) bool {
|
||||
lcaseKey := strings.ToLower(key)
|
||||
val := v.find(lcaseKey)
|
||||
casedKey := v.caseKey(key)
|
||||
val := v.find(casedKey)
|
||||
return val != nil
|
||||
}
|
||||
|
||||
|
@ -1127,11 +1129,11 @@ func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {
|
|||
// This enables one to change a name without breaking the application
|
||||
func RegisterAlias(alias string, key string) { v.RegisterAlias(alias, key) }
|
||||
func (v *Viper) RegisterAlias(alias string, key string) {
|
||||
v.registerAlias(alias, strings.ToLower(key))
|
||||
v.registerAlias(alias, v.caseKey(key))
|
||||
}
|
||||
|
||||
func (v *Viper) registerAlias(alias string, key string) {
|
||||
alias = strings.ToLower(alias)
|
||||
alias = v.caseKey(alias)
|
||||
if alias != key && alias != v.realKey(key) {
|
||||
_, exists := v.aliases[alias]
|
||||
|
||||
|
@ -1182,16 +1184,19 @@ func (v *Viper) InConfig(key string) bool {
|
|||
}
|
||||
|
||||
// SetDefault sets the default value for this key.
|
||||
// SetDefault is case-insensitive for a key.
|
||||
// SetDefault is case-insensitive for a key. This behavior can be modified
|
||||
// with viper.SetKeysCaseSensitive.
|
||||
// Default only used when no value is provided by the user via flag, config or ENV.
|
||||
func SetDefault(key string, value interface{}) { v.SetDefault(key, value) }
|
||||
func (v *Viper) SetDefault(key string, value interface{}) {
|
||||
// If alias passed in, then set the proper default
|
||||
key = v.realKey(strings.ToLower(key))
|
||||
value = toCaseInsensitiveValue(value)
|
||||
key = v.realKey(v.caseKey(key))
|
||||
if !v.caseSensitiveKeys {
|
||||
value = toCaseInsensitiveValue(value)
|
||||
}
|
||||
|
||||
path := strings.Split(key, v.keyDelim)
|
||||
lastKey := strings.ToLower(path[len(path)-1])
|
||||
lastKey := v.caseKey(path[len(path)-1])
|
||||
deepestMap := deepSearch(v.defaults, path[0:len(path)-1])
|
||||
|
||||
// set innermost value
|
||||
|
@ -1199,17 +1204,20 @@ func (v *Viper) SetDefault(key string, value interface{}) {
|
|||
}
|
||||
|
||||
// Set sets the value for the key in the override register.
|
||||
// Set is case-insensitive for a key.
|
||||
// Set is case-insensitive for a key. This behavior can be modified
|
||||
// with viper.SetKeysCaseSensitive.
|
||||
// Will be used instead of values obtained via
|
||||
// flags, config file, ENV, default, or key/value store.
|
||||
func Set(key string, value interface{}) { v.Set(key, value) }
|
||||
func (v *Viper) Set(key string, value interface{}) {
|
||||
// If alias passed in, then set the proper override
|
||||
key = v.realKey(strings.ToLower(key))
|
||||
value = toCaseInsensitiveValue(value)
|
||||
key = v.realKey(v.caseKey(key))
|
||||
if !v.caseSensitiveKeys {
|
||||
value = toCaseInsensitiveValue(value)
|
||||
}
|
||||
|
||||
path := strings.Split(key, v.keyDelim)
|
||||
lastKey := strings.ToLower(path[len(path)-1])
|
||||
lastKey := v.caseKey(path[len(path)-1])
|
||||
deepestMap := deepSearch(v.override, path[0:len(path)-1])
|
||||
|
||||
// set innermost value
|
||||
|
@ -1293,7 +1301,9 @@ func (v *Viper) MergeConfigMap(cfg map[string]interface{}) error {
|
|||
if v.config == nil {
|
||||
v.config = make(map[string]interface{})
|
||||
}
|
||||
insensitiviseMap(cfg)
|
||||
if !v.caseSensitiveKeys {
|
||||
insensitiviseMap(cfg)
|
||||
}
|
||||
mergeMaps(cfg, v.config, nil)
|
||||
return nil
|
||||
}
|
||||
|
@ -1410,14 +1420,16 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
|
|||
value, _ := v.properties.Get(key)
|
||||
// recursively build nested maps
|
||||
path := strings.Split(key, ".")
|
||||
lastKey := strings.ToLower(path[len(path)-1])
|
||||
lastKey := v.caseKey(path[len(path)-1])
|
||||
deepestMap := deepSearch(c, path[0:len(path)-1])
|
||||
// set innermost value
|
||||
deepestMap[lastKey] = value
|
||||
}
|
||||
}
|
||||
|
||||
insensitiviseMap(c)
|
||||
if !v.caseSensitiveKeys {
|
||||
insensitiviseMap(c)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -1488,10 +1500,9 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error {
|
|||
}
|
||||
|
||||
func keyExists(k string, m map[string]interface{}) string {
|
||||
lk := strings.ToLower(k)
|
||||
ck := v.caseKey(k)
|
||||
for mk := range m {
|
||||
lmk := strings.ToLower(mk)
|
||||
if lmk == lk {
|
||||
if mk == ck {
|
||||
return mk
|
||||
}
|
||||
}
|
||||
|
@ -1714,7 +1725,7 @@ func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]interfac
|
|||
m2 = cast.ToStringMap(val)
|
||||
default:
|
||||
// immediate value
|
||||
shadow[strings.ToLower(fullKey)] = true
|
||||
shadow[v.caseKey(fullKey)] = true
|
||||
continue
|
||||
}
|
||||
// recursively merge to shadow map
|
||||
|
@ -1740,7 +1751,7 @@ outer:
|
|||
}
|
||||
}
|
||||
// add key
|
||||
shadow[strings.ToLower(k)] = true
|
||||
shadow[v.caseKey(k)] = true
|
||||
}
|
||||
return shadow
|
||||
}
|
||||
|
@ -1758,7 +1769,7 @@ func (v *Viper) AllSettings() map[string]interface{} {
|
|||
continue
|
||||
}
|
||||
path := strings.Split(k, v.keyDelim)
|
||||
lastKey := strings.ToLower(path[len(path)-1])
|
||||
lastKey := v.caseKey(path[len(path)-1])
|
||||
deepestMap := deepSearch(m, path[0:len(path)-1])
|
||||
// set innermost value
|
||||
deepestMap[lastKey] = value
|
||||
|
@ -1797,6 +1808,22 @@ func (v *Viper) SetConfigPermissions(perm os.FileMode) {
|
|||
v.configPermissions = perm.Perm()
|
||||
}
|
||||
|
||||
// SetKeysCaseSensitive disables the default behaviour of
|
||||
// case-insensitivising (lowercasing) keys and preserves the key casing
|
||||
// as they are found in the config files. It is important
|
||||
// to note that operations such as set and merge when
|
||||
// case sensitivity is 'on', and whtn it is turneed 'off',
|
||||
// are incompatible. A key that is set when case sentivity
|
||||
// is 'on' may not be retrievable when case sensitivity is turned 'off',
|
||||
// as the original casing is permanently lost in the former mode.
|
||||
// That is ideally, this should only be invoked only once,
|
||||
// during initialisation, and the subsequent usage must adhere
|
||||
// to the same case sentivity.
|
||||
func SetKeysCaseSensitive(on bool) { v.SetKeysCaseSensitive(on) }
|
||||
func (v *Viper) SetKeysCaseSensitive(on bool) {
|
||||
v.caseSensitiveKeys = on
|
||||
}
|
||||
|
||||
func (v *Viper) getConfigType() string {
|
||||
if v.configType != "" {
|
||||
return v.configType
|
||||
|
@ -1840,6 +1867,15 @@ func (v *Viper) searchInPath(in string) (filename string) {
|
|||
return ""
|
||||
}
|
||||
|
||||
// caseKey cases (preserves sensitivity or lowercases) a
|
||||
// given key based on the keyCaseSensitivity config.
|
||||
func (v *Viper) caseKey(in string) (filename string) {
|
||||
if v.caseSensitiveKeys {
|
||||
return in
|
||||
}
|
||||
return strings.ToLower(in)
|
||||
}
|
||||
|
||||
// Search all configPaths for any config file.
|
||||
// Returns the first path that exists (and is a config file).
|
||||
func (v *Viper) findConfigFile() (string, error) {
|
||||
|
|
|
@ -1402,6 +1402,51 @@ R = 6
|
|||
}
|
||||
}
|
||||
|
||||
func TestCaseSensitive(t *testing.T) {
|
||||
for _, config := range []struct {
|
||||
typ string
|
||||
content string
|
||||
}{
|
||||
{"yaml", `
|
||||
aBcD: 1
|
||||
eF:
|
||||
gH: 2
|
||||
iJk: 3
|
||||
Lm:
|
||||
nO: 4
|
||||
P:
|
||||
Q: 5
|
||||
R: 6
|
||||
`},
|
||||
{"json", `{
|
||||
"aBcD": 1,
|
||||
"eF": {
|
||||
"iJk": 3,
|
||||
"Lm": {
|
||||
"P": {
|
||||
"Q": 5,
|
||||
"R": 6
|
||||
},
|
||||
"nO": 4
|
||||
},
|
||||
"gH": 2
|
||||
}
|
||||
}`},
|
||||
{"toml", `aBcD = 1
|
||||
[eF]
|
||||
gH = 2
|
||||
iJk = 3
|
||||
[eF.Lm]
|
||||
nO = 4
|
||||
[eF.Lm.P]
|
||||
Q = 5
|
||||
R = 6
|
||||
`},
|
||||
} {
|
||||
doTestCaseSensitive(t, config.typ, config.content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaseInsensitiveSet(t *testing.T) {
|
||||
Reset()
|
||||
m1 := map[string]interface{}{
|
||||
|
@ -1501,6 +1546,32 @@ func doTestCaseInsensitive(t *testing.T, typ, config string) {
|
|||
assert.Equal(t, 3, cast.ToInt(Get("ef.ijk")))
|
||||
assert.Equal(t, 4, cast.ToInt(Get("ef.lm.no")))
|
||||
assert.Equal(t, 5, cast.ToInt(Get("ef.lm.p.q")))
|
||||
}
|
||||
|
||||
func doTestCaseSensitive(t *testing.T, typ, config string) {
|
||||
Reset()
|
||||
SetConfigType(typ)
|
||||
|
||||
// Turn on case sensitivy.
|
||||
SetKeysCaseSensitive(true)
|
||||
r := strings.NewReader(config)
|
||||
if err := unmarshalReader(r, v.config); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Set("RfD", true)
|
||||
assert.Equal(t, nil, Get("rfd"))
|
||||
assert.Equal(t, true, Get("RfD"))
|
||||
assert.Equal(t, 0, cast.ToInt(Get("abcd")))
|
||||
assert.Equal(t, 1, cast.ToInt(Get("aBcD")))
|
||||
assert.Equal(t, 0, cast.ToInt(Get("ef.gh")))
|
||||
assert.Equal(t, 2, cast.ToInt(Get("eF.gH")))
|
||||
assert.Equal(t, 0, cast.ToInt(Get("ef.ijk")))
|
||||
assert.Equal(t, 3, cast.ToInt(Get("eF.iJk")))
|
||||
assert.Equal(t, 0, cast.ToInt(Get("ef.lm.no")))
|
||||
assert.Equal(t, 4, cast.ToInt(Get("eF.Lm.nO")))
|
||||
assert.Equal(t, 0, cast.ToInt(Get("ef.lm.p.q")))
|
||||
assert.Equal(t, 5, cast.ToInt(Get("eF.Lm.P.Q")))
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue