Set keys case sensitive

This commit is contained in:
wangchengdu 2020-09-22 15:38:23 +08:00
commit 21f7008c6a
5 changed files with 144 additions and 32 deletions

View file

@ -73,6 +73,8 @@ Viper uses the following precedence order. Each item takes precedence over the i
**Important:** Viper configuration keys are case insensitive. **Important:** Viper configuration keys are case insensitive.
There are ongoing discussions about making that optional. There are ongoing discussions about making that optional.
Viper configuration keys are case insensitive by default. They can be made case
sensitive with `viper.SetKeysCaseSensitive(true)`.
## Putting Values into Viper ## Putting Values into Viper

3
go.mod
View file

@ -1,4 +1,4 @@
module github.com/spf13/viper module github.com/gbunt/viper
go 1.12 go 1.12
@ -27,6 +27,7 @@ require (
github.com/spf13/cast v1.3.0 github.com/spf13/cast v1.3.0
github.com/spf13/jwalterweatherman v1.0.0 github.com/spf13/jwalterweatherman v1.0.0
github.com/spf13/pflag v1.0.3 github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.7.1
github.com/stretchr/testify v1.3.0 github.com/stretchr/testify v1.3.0
github.com/subosito/gotenv v1.2.0 github.com/subosito/gotenv v1.2.0
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect

2
go.sum
View file

@ -223,6 +223,8 @@ github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=

View file

@ -199,6 +199,7 @@ type Viper struct {
automaticEnvApplied bool automaticEnvApplied bool
envKeyReplacer StringReplacer envKeyReplacer StringReplacer
allowEmptyEnv bool allowEmptyEnv bool
caseSensitiveKeys bool
config map[string]interface{} config map[string]interface{}
override map[string]interface{} override map[string]interface{}
@ -553,7 +554,6 @@ func (v *Viper) providerPathExists(p *defaultRemoteProvider) bool {
// searchMap recursively searches for a value for path in source map. // searchMap recursively searches for a value for path in source map.
// Returns nil if not found. // 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{} { func (v *Viper) searchMap(source map[string]interface{}, path []string) interface{} {
if len(path) == 0 { if len(path) == 0 {
return source return source
@ -600,7 +600,7 @@ func (v *Viper) searchMapWithPathPrefixes(source map[string]interface{}, path []
// search for path prefixes, starting from the longest one // search for path prefixes, starting from the longest one
for i := len(path); i > 0; i-- { 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] next, ok := source[prefixKey]
if ok { if ok {
@ -722,7 +722,7 @@ func GetViper() *Viper {
} }
// Get can retrieve any value given the key to use. // 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 // 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: // place from where it is set. Viper will check in the following order:
// override, flag, env, config file, key/value store, default // override, flag, env, config file, key/value store, default
@ -731,8 +731,8 @@ func GetViper() *Viper {
func Get(key string) interface{} { return v.Get(key) } func Get(key string) interface{} { return v.Get(key) }
func (v *Viper) Get(key string) interface{} { func (v *Viper) Get(key string) interface{} {
lcaseKey := strings.ToLower(key) casedKey := v.caseKey(key)
val := v.find(lcaseKey, true) val := v.find(casedKey, true)
if val == nil { if val == nil {
return nil return nil
} }
@ -740,7 +740,7 @@ func (v *Viper) Get(key string) interface{} {
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
path := strings.Split(lcaseKey, v.keyDelim) path := strings.Split(casedKey, v.keyDelim)
defVal := v.searchMap(v.defaults, path) defVal := v.searchMap(v.defaults, path)
if defVal != nil { if defVal != nil {
valType = defVal valType = defVal
@ -778,7 +778,7 @@ func (v *Viper) Get(key string) interface{} {
} }
// Sub returns new Viper instance representing a sub tree of this instance. // 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 Sub(key string) *Viper { return v.Sub(key) }
func (v *Viper) Sub(key string) *Viper { func (v *Viper) Sub(key string) *Viper {
@ -1015,7 +1015,7 @@ func (v *Viper) BindFlagValue(key string, flag FlagValue) error {
if flag == nil { if flag == nil {
return fmt.Errorf("flag for %q is nil", key) return fmt.Errorf("flag for %q is nil", key)
} }
v.pflags[strings.ToLower(key)] = flag v.pflags[v.caseKey(key)] = flag
return nil return nil
} }
@ -1031,7 +1031,7 @@ func (v *Viper) BindEnv(input ...string) error {
return fmt.Errorf("missing key to bind to") return fmt.Errorf("missing key to bind to")
} }
key = strings.ToLower(input[0]) key = v.caseKey(input[0])
if len(input) == 1 { if len(input) == 1 {
envkey = v.mergeWithEnvPrefix(key) envkey = v.mergeWithEnvPrefix(key)
@ -1221,12 +1221,13 @@ func stringToStringConv(val string) interface{} {
} }
// IsSet checks to see if the key has been set in any of the data locations. // 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 IsSet(key string) bool { return v.IsSet(key) }
func (v *Viper) IsSet(key string) bool { func (v *Viper) IsSet(key string) bool {
lcaseKey := strings.ToLower(key) casedKey := v.caseKey(key)
val := v.find(lcaseKey, false) val := v.find(casedKey, false)
return val != nil return val != nil
} }
@ -1252,11 +1253,11 @@ func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {
func RegisterAlias(alias string, key string) { v.RegisterAlias(alias, key) } func RegisterAlias(alias string, key string) { v.RegisterAlias(alias, key) }
func (v *Viper) RegisterAlias(alias string, key string) { 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) { func (v *Viper) registerAlias(alias string, key string) {
alias = strings.ToLower(alias) alias = v.caseKey(alias)
if alias != key && alias != v.realKey(key) { if alias != key && alias != v.realKey(key) {
_, exists := v.aliases[alias] _, exists := v.aliases[alias]
@ -1308,17 +1309,20 @@ func (v *Viper) InConfig(key string) bool {
} }
// SetDefault sets the default value for this key. // 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. // 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 SetDefault(key string, value interface{}) { v.SetDefault(key, value) }
func (v *Viper) SetDefault(key string, value interface{}) { func (v *Viper) SetDefault(key string, value interface{}) {
// If alias passed in, then set the proper default // If alias passed in, then set the proper default
key = v.realKey(strings.ToLower(key)) key = v.realKey(v.caseKey(key))
value = toCaseInsensitiveValue(value) if !v.caseSensitiveKeys {
value = toCaseInsensitiveValue(value)
}
path := strings.Split(key, v.keyDelim) 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]) deepestMap := deepSearch(v.defaults, path[0:len(path)-1])
// set innermost value // set innermost value
@ -1326,18 +1330,21 @@ func (v *Viper) SetDefault(key string, value interface{}) {
} }
// Set sets the value for the key in the override register. // 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 // Will be used instead of values obtained via
// flags, config file, ENV, default, or key/value store. // flags, config file, ENV, default, or key/value store.
func Set(key string, value interface{}) { v.Set(key, value) } func Set(key string, value interface{}) { v.Set(key, value) }
func (v *Viper) Set(key string, value interface{}) { func (v *Viper) Set(key string, value interface{}) {
// If alias passed in, then set the proper override // If alias passed in, then set the proper override
key = v.realKey(strings.ToLower(key)) key = v.realKey(v.caseKey(key))
value = toCaseInsensitiveValue(value) if !v.caseSensitiveKeys {
value = toCaseInsensitiveValue(value)
}
path := strings.Split(key, v.keyDelim) 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]) deepestMap := deepSearch(v.override, path[0:len(path)-1])
// set innermost value // set innermost value
@ -1426,7 +1433,9 @@ func (v *Viper) MergeConfigMap(cfg map[string]interface{}) error {
if v.config == nil { if v.config == nil {
v.config = make(map[string]interface{}) v.config = make(map[string]interface{})
} }
insensitiviseMap(cfg) if !v.caseSensitiveKeys {
insensitiviseMap(cfg)
}
mergeMaps(cfg, v.config, nil) mergeMaps(cfg, v.config, nil)
return nil return nil
} }
@ -1566,7 +1575,7 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
value, _ := v.properties.Get(key) value, _ := v.properties.Get(key)
// recursively build nested maps // recursively build nested maps
path := strings.Split(key, ".") 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]) deepestMap := deepSearch(c, path[0:len(path)-1])
// set innermost value // set innermost value
deepestMap[lastKey] = value deepestMap[lastKey] = value
@ -1590,7 +1599,9 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
} }
} }
insensitiviseMap(c) if !v.caseSensitiveKeys {
insensitiviseMap(c)
}
return nil return nil
} }
@ -1689,10 +1700,9 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error {
} }
func keyExists(k string, m map[string]interface{}) string { func keyExists(k string, m map[string]interface{}) string {
lk := strings.ToLower(k) ck := v.caseKey(k)
for mk := range m { for mk := range m {
lmk := strings.ToLower(mk) if mk == ck {
if lmk == lk {
return mk return mk
} }
} }
@ -1921,7 +1931,7 @@ func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]interfac
m2 = cast.ToStringMap(val) m2 = cast.ToStringMap(val)
default: default:
// immediate value // immediate value
shadow[strings.ToLower(fullKey)] = true shadow[v.caseKey(fullKey)] = true
continue continue
} }
// recursively merge to shadow map // recursively merge to shadow map
@ -1947,7 +1957,7 @@ outer:
} }
} }
// add key // add key
shadow[strings.ToLower(k)] = true shadow[v.caseKey(k)] = true
} }
return shadow return shadow
} }
@ -1966,7 +1976,7 @@ func (v *Viper) AllSettings() map[string]interface{} {
continue continue
} }
path := strings.Split(k, v.keyDelim) 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]) deepestMap := deepSearch(m, path[0:len(path)-1])
// set innermost value // set innermost value
deepestMap[lastKey] = value deepestMap[lastKey] = value
@ -2009,6 +2019,22 @@ func (v *Viper) SetConfigPermissions(perm os.FileMode) {
v.configPermissions = perm.Perm() 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 { func (v *Viper) getConfigType() string {
if v.configType != "" { if v.configType != "" {
return v.configType return v.configType
@ -2058,6 +2084,15 @@ func (v *Viper) searchInPath(in string) (filename string) {
return "" 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. // Search all configPaths for any config file.
// Returns the first path that exists (and is a config file). // Returns the first path that exists (and is a config file).
func (v *Viper) findConfigFile() (string, error) { func (v *Viper) findConfigFile() (string, error) {

View file

@ -1967,6 +1967,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) { func TestCaseInsensitiveSet(t *testing.T) {
Reset() Reset()
m1 := map[string]interface{}{ m1 := map[string]interface{}{
@ -2070,6 +2115,33 @@ func doTestCaseInsensitive(t *testing.T, typ, config string) {
assert.Equal(t, 5, cast.ToInt(Get("ef.lm.p.q"))) 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")))
}
func newViperWithConfigFile(t *testing.T) (*Viper, string, func()) { func newViperWithConfigFile(t *testing.T) (*Viper, string, func()) {
watchDir, err := ioutil.TempDir("", "") watchDir, err := ioutil.TempDir("", "")
require.Nil(t, err) require.Nil(t, err)