From a12e6c830050c3f74c5d6513b16b55c2f8d54c1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Fri, 9 Aug 2019 17:08:41 +0200 Subject: [PATCH] (#373) Remove key insensitivity --- README.md | 4 +-- util.go | 55 -------------------------------- util_test.go | 54 ------------------------------- viper.go | 53 ++++++++++++++----------------- viper_test.go | 88 +++++++++++++++++++++++++++------------------------ 5 files changed, 73 insertions(+), 181 deletions(-) delete mode 100644 util_test.go diff --git a/README.md b/README.md index 171f51c..8fe4496 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ item below it: * key/value store * default -Viper configuration keys are case insensitive. +Viper configuration keys are case sensitive. ## Putting Values into Viper @@ -512,7 +512,7 @@ has been provided. Example: ```go -viper.GetString("logfile") // case-insensitive Setting & Getting +viper.GetString("logfile") // case-sensitive Setting & Getting if viper.GetBool("verbose") { fmt.Println("verbose enabled") } diff --git a/util.go b/util.go index 952cad4..a434a5d 100644 --- a/util.go +++ b/util.go @@ -33,61 +33,6 @@ func (pe ConfigParseError) Error() string { return fmt.Sprintf("While parsing config: %s", pe.err.Error()) } -// toCaseInsensitiveValue checks if the value is a map; -// if so, create a copy and lower-case the keys recursively. -func toCaseInsensitiveValue(value interface{}) interface{} { - switch v := value.(type) { - case map[interface{}]interface{}: - value = copyAndInsensitiviseMap(cast.ToStringMap(v)) - case map[string]interface{}: - value = copyAndInsensitiviseMap(v) - } - - return value -} - -// copyAndInsensitiviseMap behaves like insensitiviseMap, but creates a copy of -// any map it makes case insensitive. -func copyAndInsensitiviseMap(m map[string]interface{}) map[string]interface{} { - nm := make(map[string]interface{}) - - for key, val := range m { - lkey := strings.ToLower(key) - switch v := val.(type) { - case map[interface{}]interface{}: - nm[lkey] = copyAndInsensitiviseMap(cast.ToStringMap(v)) - case map[string]interface{}: - nm[lkey] = copyAndInsensitiviseMap(v) - default: - nm[lkey] = v - } - } - - return nm -} - -func insensitiviseMap(m map[string]interface{}) { - for key, val := range m { - switch val.(type) { - case map[interface{}]interface{}: - // nested map: cast and recursively insensitivise - val = cast.ToStringMap(val) - insensitiviseMap(val.(map[string]interface{})) - case map[string]interface{}: - // nested map: recursively insensitivise - insensitiviseMap(val.(map[string]interface{})) - } - - lower := strings.ToLower(key) - if key != lower { - // remove old key (not lower-cased) - delete(m, key) - } - // update map - m[lower] = val - } -} - func absPathify(inPath string) string { jww.INFO.Println("Trying to resolve absolute path to", inPath) diff --git a/util_test.go b/util_test.go deleted file mode 100644 index 0af80bb..0000000 --- a/util_test.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright © 2016 Steve Francia . -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -// Viper is a application configuration system. -// It believes that applications can be configured a variety of ways -// via flags, ENVIRONMENT variables, configuration files retrieved -// from the file system, or a remote key/value store. - -package viper - -import ( - "reflect" - "testing" -) - -func TestCopyAndInsensitiviseMap(t *testing.T) { - var ( - given = map[string]interface{}{ - "Foo": 32, - "Bar": map[interface{}]interface { - }{ - "ABc": "A", - "cDE": "B"}, - } - expected = map[string]interface{}{ - "foo": 32, - "bar": map[string]interface { - }{ - "abc": "A", - "cde": "B"}, - } - ) - - got := copyAndInsensitiviseMap(given) - - if !reflect.DeepEqual(got, expected) { - t.Fatalf("Got %q\nexpected\n%q", got, expected) - } - - if _, ok := given["foo"]; ok { - t.Fatal("Input map changed") - } - - if _, ok := given["bar"]; ok { - t.Fatal("Input map changed") - } - - m := given["Bar"].(map[interface{}]interface{}) - if _, ok := m["ABc"]; !ok { - t.Fatal("Input map changed") - } -} diff --git a/viper.go b/viper.go index c65881a..700a585 100644 --- a/viper.go +++ b/viper.go @@ -493,7 +493,7 @@ 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. +// Note: This assumes that the path entries and map keys are cased sensitive. func (v *Viper) searchMap(source map[string]interface{}, path []string) interface{} { if len(path) == 0 { return source @@ -532,7 +532,7 @@ func (v *Viper) searchMap(source map[string]interface{}, path []string) interfac // This should be useful only at config level (other maps may not contain dots // in their keys). // -// Note: This assumes that the path entries and map keys are lower cased. +// Note: This assumes that the path entries and map keys are case sensitive. func (v *Viper) searchMapWithPathPrefixes(source map[string]interface{}, path []string) interface{} { if len(path) == 0 { return source @@ -540,7 +540,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 := strings.Join(path[0:i], v.keyDelim) next, ok := source[prefixKey] if ok { @@ -661,7 +661,7 @@ func GetViper() *Viper { } // Get can retrieve any value given the key to use. -// Get is case-insensitive for a key. +// Get is case-sensitive for a key. // 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 @@ -669,7 +669,7 @@ 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) + lcaseKey := key val := v.find(lcaseKey) if val == nil { return nil @@ -716,7 +716,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 is case-sensitive for a key. func Sub(key string) *Viper { return v.Sub(key) } func (v *Viper) Sub(key string) *Viper { subv := New() @@ -948,7 +948,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[key] = flag return nil } @@ -963,7 +963,7 @@ func (v *Viper) BindEnv(input ...string) error { return fmt.Errorf("BindEnv missing key to bind to") } - key = strings.ToLower(input[0]) + key = input[0] if len(input) == 1 { envkey = v.mergeWithEnvPrefix(key) @@ -980,7 +980,7 @@ 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. +// Note: this assumes a case-sensitive key given. func (v *Viper) find(lcaseKey string) interface{} { var ( @@ -1120,10 +1120,10 @@ 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-sensitive for a key. func IsSet(key string) bool { return v.IsSet(key) } func (v *Viper) IsSet(key string) bool { - lcaseKey := strings.ToLower(key) + lcaseKey := key val := v.find(lcaseKey) return val != nil } @@ -1147,11 +1147,10 @@ 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, key) } func (v *Viper) registerAlias(alias string, key string) { - alias = strings.ToLower(alias) if alias != key && alias != v.realKey(key) { _, exists := v.aliases[alias] @@ -1202,16 +1201,15 @@ 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-sensitive for a key. // 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(key) path := strings.Split(key, v.keyDelim) - lastKey := strings.ToLower(path[len(path)-1]) + lastKey := path[len(path)-1] deepestMap := deepSearch(v.defaults, path[0:len(path)-1]) // set innermost value @@ -1219,17 +1217,16 @@ 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-sensitive for a key. // 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(key) path := strings.Split(key, v.keyDelim) - lastKey := strings.ToLower(path[len(path)-1]) + lastKey := path[len(path)-1] deepestMap := deepSearch(v.override, path[0:len(path)-1]) // set innermost value @@ -1313,7 +1310,6 @@ func (v *Viper) MergeConfigMap(cfg map[string]interface{}) error { if v.config == nil { v.config = make(map[string]interface{}) } - insensitiviseMap(cfg) mergeMaps(cfg, v.config, nil) return nil } @@ -1439,14 +1435,13 @@ 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 := path[len(path)-1] deepestMap := deepSearch(c, path[0:len(path)-1]) // set innermost value deepestMap[lastKey] = value } } - insensitiviseMap(c) return nil } @@ -1532,9 +1527,9 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error { } func keyExists(k string, m map[string]interface{}) string { - lk := strings.ToLower(k) + lk := k for mk := range m { - lmk := strings.ToLower(mk) + lmk := mk if lmk == lk { return mk } @@ -1758,7 +1753,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[fullKey] = true continue } // recursively merge to shadow map @@ -1784,7 +1779,7 @@ outer: } } // add key - shadow[strings.ToLower(k)] = true + shadow[k] = true } return shadow } @@ -1802,7 +1797,7 @@ func (v *Viper) AllSettings() map[string]interface{} { continue } path := strings.Split(k, v.keyDelim) - lastKey := strings.ToLower(path[len(path)-1]) + lastKey := path[len(path)-1] deepestMap := deepSearch(m, path[0:len(path)-1]) // set innermost value deepestMap[lastKey] = value diff --git a/viper_test.go b/viper_test.go index 6d25b93..3b9126e 100644 --- a/viper_test.go +++ b/viper_test.go @@ -66,7 +66,7 @@ dob = 1979-05-27T07:32:00Z # First class dates? Why not?`) var dotenvExample = []byte(` TITLE_DOTENV="DotEnv Example" TYPE_DOTENV=donut -NAME_DOTENV=Cake`) +name_dotenv=Cake`) var jsonExample = []byte(`{ "id": "0001", @@ -295,7 +295,7 @@ func TestUnmarshaling(t *testing.T) { assert.False(t, InConfig("state")) assert.Equal(t, "steve", Get("name")) assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, Get("hobbies")) - assert.Equal(t, map[string]interface{}{"jacket": "leather", "TROUSERS": "denim", "pants": map[string]interface{}{"size": "large"}}, Get("clothing")) + assert.Equal(t, map[interface{}]interface{}{"TROUSERS": "denim", "jacket": "leather", "pants": map[interface{}]interface{}{"size": "large"}}, Get("clothing")) assert.Equal(t, 35, Get("age")) } @@ -360,7 +360,7 @@ func TestTOML(t *testing.T) { func TestDotEnv(t *testing.T) { initDotEnv() - assert.Equal(t, "DotEnv Example", Get("title_dotenv")) + assert.Equal(t, "DotEnv Example", Get("TITLE_DOTENV")) } func TestHCL(t *testing.T) { @@ -491,11 +491,11 @@ func TestSetEnvKeyReplacer(t *testing.T) { func TestAllKeys(t *testing.T) { initConfigs() - ks := sort.StringSlice{"title", "newkey", "owner.organization", "owner.dob", "owner.bio", "name", "beard", "ppu", "batters.batter", "hobbies", "clothing.jacket", "clothing.TROUSERS", "clothing.pants.size", "age", "hacker", "id", "type", "eyes", "p_id", "p_ppu", "p_batters.batter.type", "p_type", "p_name", "foos", - "title_dotenv", "type_dotenv", "name_dotenv", + ks := sort.StringSlice{"title", "newkey", "owner.organization", "owner.dob", "owner.Bio", "name", "beard", "ppu", "batters.batter", "hobbies", "clothing.jacket", "clothing.TROUSERS", "clothing.pants.size", "age", "Hacker", "id", "type", "eyes", "p_id", "p_ppu", "p_batters.batter.type", "p_type", "p_name", "foos", + "TITLE_DOTENV", "TYPE_DOTENV", "name_dotenv", } dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") - all := map[string]interface{}{"owner": map[string]interface{}{"organization": "MongoDB", "bio": "MongoDB Chief Developer Advocate & Hacker at Large", "dob": dob}, "title": "TOML Example", "ppu": 0.55, "eyes": "brown", "clothing": map[string]interface{}{"TROUSERS": "denim", "jacket": "leather", "pants": map[string]interface{}{"size": "large"}}, "id": "0001", "batters": map[string]interface{}{"batter": []interface{}{map[string]interface{}{"type": "Regular"}, map[string]interface{}{"type": "Chocolate"}, map[string]interface{}{"type": "Blueberry"}, map[string]interface{}{"type": "Devil's Food"}}}, "hacker": true, "beard": true, "hobbies": []interface{}{"skateboarding", "snowboarding", "go"}, "age": 35, "type": "donut", "newkey": "remote", "name": "Cake", "p_id": "0001", "p_ppu": "0.55", "p_name": "Cake", "p_batters": map[string]interface{}{"batter": map[string]interface{}{"type": "Regular"}}, "p_type": "donut", "foos": []map[string]interface{}{map[string]interface{}{"foo": []map[string]interface{}{map[string]interface{}{"key": 1}, map[string]interface{}{"key": 2}, map[string]interface{}{"key": 3}, map[string]interface{}{"key": 4}}}}, "title_dotenv": "DotEnv Example", "type_dotenv": "donut", "name_dotenv": "Cake"} + all := map[string]interface{}{"owner": map[string]interface{}{"organization": "MongoDB", "Bio": "MongoDB Chief Developer Advocate & Hacker at Large", "dob": dob}, "title": "TOML Example", "ppu": 0.55, "eyes": "brown", "clothing": map[string]interface{}{"TROUSERS": "denim", "jacket": "leather", "pants": map[string]interface{}{"size": "large"}}, "id": "0001", "batters": map[string]interface{}{"batter": []interface{}{map[string]interface{}{"type": "Regular"}, map[string]interface{}{"type": "Chocolate"}, map[string]interface{}{"type": "Blueberry"}, map[string]interface{}{"type": "Devil's Food"}}}, "Hacker": true, "beard": true, "hobbies": []interface{}{"skateboarding", "snowboarding", "go"}, "age": 35, "type": "donut", "newkey": "remote", "name": "Cake", "p_id": "0001", "p_ppu": "0.55", "p_name": "Cake", "p_batters": map[string]interface{}{"batter": map[string]interface{}{"type": "Regular"}}, "p_type": "donut", "foos": []map[string]interface{}{map[string]interface{}{"foo": []map[string]interface{}{map[string]interface{}{"key": 1}, map[string]interface{}{"key": 2}, map[string]interface{}{"key": 3}, map[string]interface{}{"key": 4}}}}, "TITLE_DOTENV": "DotEnv Example", "TYPE_DOTENV": "donut", "name_dotenv": "Cake"} allkeys := sort.StringSlice(AllKeys()) allkeys.Sort() @@ -526,7 +526,7 @@ func TestAliasesOfAliases(t *testing.T) { Set("Title", "Checking Case") RegisterAlias("Foo", "Bar") RegisterAlias("Bar", "Title") - assert.Equal(t, "Checking Case", Get("FOO")) + assert.Equal(t, "Checking Case", Get("Foo")) } func TestRecursiveAliases(t *testing.T) { @@ -773,7 +773,8 @@ func TestBoundCaseSensitivity(t *testing.T) { BindEnv("eYEs", "TURTLE_EYES") os.Setenv("TURTLE_EYES", "blue") - assert.Equal(t, "blue", Get("eyes")) + assert.Equal(t, "blue", Get("eYEs")) + assert.Nil(t, Get("eyeS")) var testString = "green" var testValue = newStringValue(testString, &testString) @@ -785,7 +786,8 @@ func TestBoundCaseSensitivity(t *testing.T) { } BindPFlag("eYEs", flag) - assert.Equal(t, "green", Get("eyes")) + assert.Equal(t, "green", Get("eYEs")) + assert.Nil(t, Get("Eyes")) } @@ -846,7 +848,7 @@ func TestFindsNestedKeys(t *testing.T) { }, "TITLE_DOTENV": "DotEnv Example", "TYPE_DOTENV": "donut", - "NAME_DOTENV": "Cake", + "name_dotenv": "Cake", "title": "TOML Example", "newkey": "remote", "batters": map[string]interface{}{ @@ -867,19 +869,19 @@ func TestFindsNestedKeys(t *testing.T) { "age": 35, "owner": map[string]interface{}{ "organization": "MongoDB", - "bio": "MongoDB Chief Developer Advocate & Hacker at Large", + "Bio": "MongoDB Chief Developer Advocate & Hacker at Large", "dob": dob, }, - "owner.bio": "MongoDB Chief Developer Advocate & Hacker at Large", + "owner.Bio": "MongoDB Chief Developer Advocate & Hacker at Large", "type": "donut", "id": "0001", "name": "Cake", - "hacker": true, + "Hacker": true, "ppu": 0.55, - "clothing": map[string]interface{}{ + "clothing": map[interface{}]interface{}{ "jacket": "leather", "TROUSERS": "denim", - "pants": map[string]interface{}{ + "pants": map[interface{}]interface{}{ "size": "large", }, }, @@ -925,7 +927,7 @@ func TestReadBufConfig(t *testing.T) { assert.False(t, v.InConfig("state")) assert.Equal(t, "steve", v.Get("name")) assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, v.Get("hobbies")) - assert.Equal(t, map[string]interface{}{"jacket": "leather", "TROUSERS": "denim", "pants": map[string]interface{}{"size": "large"}}, v.Get("clothing")) + assert.Equal(t, map[interface{}]interface{}{"TROUSERS": "denim", "jacket": "leather", "pants": map[interface{}]interface{}{"size": "large"}}, v.Get("clothing")) assert.Equal(t, 35, v.Get("age")) } @@ -1164,7 +1166,7 @@ func TestWriteConfigTOML(t *testing.T) { } assert.Equal(t, v.GetString("title"), v2.GetString("title")) - assert.Equal(t, v.GetString("owner.bio"), v2.GetString("owner.bio")) + assert.Equal(t, v.GetString("owner.Bio"), v2.GetString("owner.Bio")) assert.Equal(t, v.GetString("owner.dob"), v2.GetString("owner.dob")) assert.Equal(t, v.GetString("owner.organization"), v2.GetString("owner.organization")) } @@ -1206,15 +1208,15 @@ func TestWriteConfigDotEnv(t *testing.T) { assert.Equal(t, v.GetString("kind"), v2.GetString("kind")) } -var yamlWriteExpected = []byte(`age: 35 +var yamlWriteExpected = []byte(`Hacker: true +age: 35 beard: true clothing: + TROUSERS: denim jacket: leather pants: size: large - TROUSERS: denim eyes: brown -hacker: true hobbies: - skateboarding - snowboarding @@ -1225,6 +1227,9 @@ name: steve func TestWriteConfigYAML(t *testing.T) { v := New() fs := afero.NewMemMapFs() + a := afero.Afero{ + Fs: fs, + } v.SetFs(fs) v.SetConfigName("c") v.SetConfigType("yaml") @@ -1235,7 +1240,7 @@ func TestWriteConfigYAML(t *testing.T) { if err := v.WriteConfigAs("c.yaml"); err != nil { t.Fatal(err) } - read, err := afero.ReadFile(fs, "c.yaml") + read, err := a.ReadFile("c.yaml") if err != nil { t.Fatal(err) } @@ -1405,8 +1410,8 @@ func TestMergeConfigMap(t *testing.T) { assert(37890) update := map[string]interface{}{ - "Hello": map[string]interface{}{ - "Pop": 1234, + "hello": map[interface{}]interface{}{ + "pop": 1234, }, "World": map[interface{}]interface{}{ "Rock": 345, @@ -1417,7 +1422,7 @@ func TestMergeConfigMap(t *testing.T) { t.Fatal(err) } - if rock := v.GetInt("world.rock"); rock != 345 { + if rock := v.GetInt("World.Rock"); rock != 345 { t.Fatal("Got rock:", rock) } @@ -1497,7 +1502,7 @@ func TestDotParameter(t *testing.T) { assert.Equal(t, expected, actual) } -func TestCaseInsensitive(t *testing.T) { +func TestCaseSensitive(t *testing.T) { for _, config := range []struct { typ string content string @@ -1538,7 +1543,7 @@ Q = 5 R = 6 `}, } { - doTestCaseInsensitive(t, config.typ, config.content) + doTestCaseSensitive(t, config.typ, config.content) } } @@ -1567,15 +1572,15 @@ func TestCaseInsensitiveSet(t *testing.T) { SetDefault("Number2", 52) // Verify SetDefault - if v := Get("number2"); v != 52 { + if v := Get("Number2"); v != 52 { t.Fatalf("Expected 52 got %q", v) } - if v := Get("given2.foo"); v != 52 { + if v := Get("Given2.Foo"); v != 52 { t.Fatalf("Expected 52 got %q", v) } - if v := Get("given2.bar.bcd"); v != "A" { + if v := Get("Given2.Bar.bCd"); v != "A" { t.Fatalf("Expected A got %q", v) } @@ -1584,15 +1589,15 @@ func TestCaseInsensitiveSet(t *testing.T) { } // Verify Set - if v := Get("number1"); v != 42 { + if v := Get("Number1"); v != 42 { t.Fatalf("Expected 42 got %q", v) } - if v := Get("given1.foo"); v != 32 { + if v := Get("Given1.Foo"); v != 32 { t.Fatalf("Expected 32 got %q", v) } - if v := Get("given1.bar.abc"); v != "A" { + if v := Get("Given1.Bar.ABc"); v != "A" { t.Fatalf("Expected A got %q", v) } @@ -1630,17 +1635,18 @@ func TestParseNested(t *testing.T) { assert.Equal(t, 200*time.Millisecond, items[0].Nested.Delay) } -func doTestCaseInsensitive(t *testing.T, typ, config string) { +func doTestCaseSensitive(t *testing.T, typ, config string) { initConfig(typ, config) Set("RfD", true) - assert.Equal(t, true, Get("rfd")) - assert.Equal(t, true, Get("rFD")) - assert.Equal(t, 1, cast.ToInt(Get("abcd"))) - assert.Equal(t, 1, cast.ToInt(Get("Abcd"))) - assert.Equal(t, 2, cast.ToInt(Get("ef.gh"))) - 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"))) + assert.Nil(t, Get("rfd")) + assert.Nil(t, Get("rFD")) + assert.Nil(t, Get("RFD")) + assert.Equal(t, true, Get("RfD")) + assert.Equal(t, 1, cast.ToInt(Get("aBcD"))) + assert.Equal(t, 2, cast.ToInt(Get("eF.gH"))) + 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"))) }