Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

case sensitive adjustments #1

Merged
merged 2 commits into from
Jan 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 6 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
> ## Viper v2 feedback
> Viper is heading towards v2 and we would love to hear what _**you**_ would like to see in it. Share your thoughts here: https://forms.gle/R6faU74qPRPAzchZ9
>
> **Thank you!**

![Viper](.github/logo.png?raw=true)

[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go#configuration)
Expand Down Expand Up @@ -77,9 +72,8 @@ Viper uses the following precedence order. Each item takes precedence over the i
* key/value store
* default

**Important:** Viper configuration keys are case insensitive.
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

Expand Down Expand Up @@ -843,17 +837,12 @@ application foundation needs.

Is there a better name for a [commander](http://en.wikipedia.org/wiki/Cobra_Commander)?

### Does Viper support case sensitive keys?

**tl;dr:** No.

Viper merges configuration from various sources, many of which are either case insensitive or uses different casing than the rest of the sources (eg. env vars).
In order to provide the best experience when using multiple sources, the decision has been made to make all keys case insensitive.

There has been several attempts to implement case sensitivity, but unfortunately it's not that trivial. We might take a stab at implementing it in [Viper v2](https://github.com/spf13/viper/issues/772), but despite the initial noise, it does not seem to be requested that much.
### Does Viper support case-sensitive keys?

You can vote for case sensitivity by filling out this feedback form: https://forms.gle/R6faU74qPRPAzchZ9
**tl;dr:** Yes.

Keys are case-insensitive by default. They can be made case-sensitive with
`viper.SetKeysCaseSensitive(true)`.

## Troubleshooting

Expand Down
113 changes: 74 additions & 39 deletions viper.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ type Viper struct {
automaticEnvApplied bool
envKeyReplacer StringReplacer
allowEmptyEnv bool
keysCaseSensitive bool

config map[string]interface{}
override map[string]interface{}
Expand Down Expand Up @@ -554,7 +555,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
Expand Down Expand Up @@ -601,7 +601,7 @@ func (v *Viper) searchIndexableWithPathPrefixes(source interface{}, path []strin

// 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))

var val interface{}
switch sourceIndexable := source.(type) {
Expand Down Expand Up @@ -780,7 +780,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 a key is determined by viper.keysCaseSensitive.
// 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
Expand All @@ -789,16 +789,16 @@ func GetViper() *Viper {
func Get(key string) interface{} { return v.Get(key) }

func (v *Viper) Get(key string) interface{} {
lcaseKey := strings.ToLower(key)
val := v.find(lcaseKey, true)
casedKey := v.caseKey(key)
val := v.find(casedKey, true)
if val == nil {
return nil
}

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
Expand Down Expand Up @@ -836,7 +836,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 a key is determined by viper.KeysCaseSensitive.
func Sub(key string) *Viper { return v.Sub(key) }

func (v *Viper) Sub(key string) *Viper {
Expand Down Expand Up @@ -1076,7 +1076,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
}

Expand All @@ -1093,7 +1093,7 @@ func (v *Viper) BindEnv(input ...string) error {
return fmt.Errorf("missing key to bind to")
}

key := strings.ToLower(input[0])
key := v.caseKey(input[0])

if len(input) == 1 {
v.env[key] = append(v.env[key], v.mergeWithEnvPrefix(key))
Expand All @@ -1112,12 +1112,13 @@ func (v *Viper) BindEnv(input ...string) error {
// Lastly, if no value was found and flagDefault is true, and if the key
// corresponds to a flag, the flag's default value is returned.
//
// Note: this assumes a lower-cased key given.
func (v *Viper) find(lcaseKey string, flagDefault bool) 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, flagDefault bool) interface{} {
var (
val interface{}
exists bool
path = strings.Split(lcaseKey, v.keyDelim)
path = strings.Split(key, v.keyDelim)
nested = len(path) > 1
)

Expand All @@ -1127,8 +1128,8 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) 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
Expand All @@ -1141,7 +1142,7 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) 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":
Expand Down Expand Up @@ -1172,14 +1173,14 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) 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
}
}
envkeys, exists := v.env[lcaseKey]
envkeys, exists := v.env[key]
if exists {
for _, envkey := range envkeys {
if val, ok := v.getEnv(envkey); ok {
Expand Down Expand Up @@ -1221,7 +1222,7 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {
if flagDefault {
// last chance: if no value is found and a flag does exist for the key,
// get the flag's default value even if the flag's value has not been set.
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())
Expand Down Expand Up @@ -1287,8 +1288,8 @@ func stringToStringConv(val string) interface{} {
func IsSet(key string) bool { return v.IsSet(key) }

func (v *Viper) IsSet(key string) bool {
lcaseKey := strings.ToLower(key)
val := v.find(lcaseKey, false)
casedKey := v.caseKey(key)
val := v.find(casedKey, false)
return val != nil
}

Expand All @@ -1314,11 +1315,11 @@ func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {
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]

Expand Down Expand Up @@ -1370,36 +1371,42 @@ 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.keysCaseSensitive {
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
deepestMap[lastKey] = value
}

// 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.keysCaseSensitive {
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
Expand Down Expand Up @@ -1488,7 +1495,9 @@ func (v *Viper) MergeConfigMap(cfg map[string]interface{}) error {
if v.config == nil {
v.config = make(map[string]interface{})
}
insensitiviseMap(cfg)
if !v.keysCaseSensitive {
insensitiviseMap(cfg)
}
mergeMaps(cfg, v.config, nil)
return nil
}
Expand Down Expand Up @@ -1628,7 +1637,7 @@ 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
Expand All @@ -1652,7 +1661,9 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
}
}

insensitiviseMap(c)
if !v.keysCaseSensitive {
insensitiviseMap(c)
}
return nil
}

Expand Down Expand Up @@ -1751,10 +1762,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
}
}
Expand Down Expand Up @@ -1991,7 +2001,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
Expand All @@ -2017,7 +2027,7 @@ outer:
}
}
// add key
shadow[strings.ToLower(k)] = true
shadow[v.caseKey(k)] = true
}
return shadow
}
Expand All @@ -2036,7 +2046,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
Expand Down Expand Up @@ -2072,6 +2082,22 @@ func (v *Viper) SetConfigType(in string) {
}
}

// 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.keysCaseSensitive = on
}

// SetConfigPermissions sets the permissions for the config file.
func SetConfigPermissions(perm os.FileMode) { v.SetConfigPermissions(perm) }

Expand Down Expand Up @@ -2128,6 +2154,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.keysCaseSensitive {
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) {
Expand Down
Loading