Skip to content
This repository was archived by the owner on Dec 15, 2020. It is now read-only.

Commit 1bdadcb

Browse files
authored
feat: add explicit typings for keys (#14)
1 parent 37827a3 commit 1bdadcb

2 files changed

Lines changed: 109 additions & 66 deletions

File tree

viper.go

Lines changed: 62 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ type Viper struct {
206206
config map[string]interface{}
207207
override map[string]interface{}
208208
defaults map[string]interface{}
209+
types map[string]interface{}
209210
kvstore map[string]interface{}
210211
pflags map[string]FlagValue
211212
env map[string]string
@@ -237,6 +238,7 @@ func New() *Viper {
237238
v.config = make(map[string]interface{})
238239
v.override = make(map[string]interface{})
239240
v.defaults = make(map[string]interface{})
241+
v.types = make(map[string]interface{})
240242
v.kvstore = make(map[string]interface{})
241243
v.previousValues = make(map[string]interface{})
242244
v.pflags = make(map[string]FlagValue)
@@ -849,44 +851,49 @@ func (v *Viper) Get(key string) interface{} {
849851
return nil
850852
}
851853

852-
if v.typeByDefValue {
853-
// TODO(bep) this branch isn't covered by a single test.
854-
valType := val
854+
valType := val
855855

856-
v.lock.RLock()
857-
path := strings.Split(lcaseKey, v.keyDelim)
858-
defVal := v.searchMap(v.defaults, path)
859-
if defVal != nil {
860-
valType = defVal
861-
}
856+
v.lock.RLock()
857+
path := strings.Split(lcaseKey, v.keyDelim)
858+
var valT interface{}
859+
if typeVal := v.searchMap(v.types, path); typeVal != nil {
860+
valT = typeVal
861+
} else if v.typeByDefValue {
862+
valT = v.searchMap(v.defaults, path)
863+
} else {
864+
// no typeVal set and typeDefByValue also not set - no conversion needed
862865
v.lock.RUnlock()
866+
return val
867+
}
868+
v.lock.RUnlock()
863869

864-
switch valType.(type) {
865-
case bool:
866-
val = cast.ToBool(val)
867-
case string:
868-
return cast.ToString(val)
869-
case int32, int16, int8, int:
870-
return cast.ToInt(val)
871-
case uint:
872-
return cast.ToUint(val)
873-
case uint32:
874-
return cast.ToUint32(val)
875-
case uint64:
876-
return cast.ToUint64(val)
877-
case int64:
878-
return cast.ToInt64(val)
879-
case float64, float32:
880-
return cast.ToFloat64(val)
881-
case time.Time:
882-
return cast.ToTime(val)
883-
case time.Duration:
884-
return cast.ToDuration(val)
885-
case []string:
886-
return cast.ToStringSlice(val)
887-
case []int:
888-
return cast.ToIntSlice(val)
889-
}
870+
valType = valT
871+
872+
switch valType.(type) {
873+
case bool:
874+
val = cast.ToBool(val)
875+
case string:
876+
return cast.ToString(val)
877+
case int32, int16, int8, int:
878+
return cast.ToInt(val)
879+
case uint:
880+
return cast.ToUint(val)
881+
case uint32:
882+
return cast.ToUint32(val)
883+
case uint64:
884+
return cast.ToUint64(val)
885+
case int64:
886+
return cast.ToInt64(val)
887+
case float64, float32:
888+
return cast.ToFloat64(val)
889+
case time.Time:
890+
return cast.ToTime(val)
891+
case time.Duration:
892+
return cast.ToDuration(val)
893+
case []string:
894+
return cast.ToStringSlice(val)
895+
case []int:
896+
return cast.ToIntSlice(val)
890897
}
891898

892899
return val
@@ -1428,21 +1435,14 @@ func (v *Viper) InConfig(key string) bool {
14281435
return exists
14291436
}
14301437

1431-
// SetDefault sets the default value for this key.
1432-
// SetDefault is case-insensitive for a key.
1433-
// Default only used when no value is provided by the user via flag, config or ENV.
1434-
func SetDefault(key string, value interface{}) { v.SetDefault(key, value) }
1435-
func (v *Viper) SetDefault(key string, value interface{}) {
1436-
// We're clearing the whole cache because nested keys may cause issues if only the key is evicted.
1437-
1438-
// If alias passed in, then set the proper default
1438+
func (v *Viper) setInMap(key string, value interface{}, target map[string]interface{}) {
14391439
key = v.realKey(strings.ToLower(key))
14401440
value = toCaseInsensitiveValue(value)
14411441

14421442
v.lock.RLock()
14431443
path := strings.Split(key, v.keyDelim)
14441444
lastKey := strings.ToLower(path[len(path)-1])
1445-
deepestMap := deepSearch(v.defaults, path[0:len(path)-1])
1445+
deepestMap := deepSearch(target, path[0:len(path)-1])
14461446
v.lock.RUnlock()
14471447

14481448
v.lock.Lock()
@@ -1452,29 +1452,30 @@ func (v *Viper) SetDefault(key string, value interface{}) {
14521452
v.lock.Unlock()
14531453
}
14541454

1455+
// SetDefault sets the default value for this key.
1456+
// SetDefault is case-insensitive for a key.
1457+
// Default only used when no value is provided by the user via flag, config or ENV.
1458+
func SetDefault(key string, value interface{}) { v.SetDefault(key, value) }
1459+
func (v *Viper) SetDefault(key string, value interface{}) {
1460+
v.setInMap(key, value, v.defaults)
1461+
}
1462+
1463+
// SetType sets the type for this key.
1464+
// This type is used for type conversions, e.g. a slice from an env var
1465+
// This function allows the default to be nil while still enabling those type conversions configured
1466+
// through SetTypeByDefaultValue
1467+
func SetType(key string, t interface{}) { v.SetType(key, t) }
1468+
func (v *Viper) SetType(key string, t interface{}) {
1469+
v.setInMap(key, t, v.types)
1470+
}
1471+
14551472
// Set sets the value for the key in the override register.
14561473
// Set is case-insensitive for a key.
14571474
// Will be used instead of values obtained via
14581475
// flags, config file, ENV, default, or key/value store.
14591476
func Set(key string, value interface{}) { v.Set(key, value) }
14601477
func (v *Viper) Set(key string, value interface{}) {
1461-
// We're clearing the whole cache because nested keys may cause issues if only the key is evicted.
1462-
1463-
// If alias passed in, then set the proper override
1464-
key = v.realKey(strings.ToLower(key))
1465-
value = toCaseInsensitiveValue(value)
1466-
1467-
v.lock.RLock()
1468-
path := strings.Split(key, v.keyDelim)
1469-
lastKey := strings.ToLower(path[len(path)-1])
1470-
deepestMap := deepSearch(v.override, path[0:len(path)-1])
1471-
v.lock.RUnlock()
1472-
1473-
// set innermost value
1474-
v.lock.Lock()
1475-
v.cache.Clear()
1476-
deepestMap[lastKey] = value
1477-
v.lock.Unlock()
1478+
v.setInMap(key, value, v.override)
14781479
}
14791480

14801481
// ReadInConfig will discover and load the configuration file from disk

viper_test.go

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -957,22 +957,23 @@ func TestBindPFlagsIntSlice(t *testing.T) {
957957
func TestBindPFlag(t *testing.T) {
958958
var testString = "testing"
959959
var testValue = newStringValue(testString, &testString)
960+
testViperKey := "testvalue"
960961

961962
flag := &pflag.Flag{
962963
Name: "testflag",
963964
Value: testValue,
964965
Changed: false,
965966
}
966967

967-
BindPFlag("testvalue", flag)
968+
require.NoError(t, BindPFlag(testViperKey, flag))
968969

969-
assert.Equal(t, testString, Get("testvalue"))
970+
assert.Equal(t, testString, Get(testViperKey))
970971

971-
BindPFlag("testvalue", flag) // hack for pflag usage
972-
flag.Value.Set("testing_mutate")
972+
require.NoError(t, BindPFlag("testvalue", flag)) // hack for pflag usage
973+
require.NoError(t, flag.Value.Set("testing_mutate"))
973974
flag.Changed = true // hack for pflag usage
974975

975-
assert.Equal(t, "testing_mutate", Get("testvalue"))
976+
assert.Equal(t, "testing_mutate", Get(testViperKey))
976977
}
977978

978979
func TestBoundCaseSensitivity(t *testing.T) {
@@ -2338,6 +2339,47 @@ func TestConfigChangedAt(t *testing.T) {
23382339
})
23392340
}
23402341

2342+
func TestTypeConversion(t *testing.T) {
2343+
t.Run("case=converts string to string slice length 1", func(t *testing.T) {
2344+
Reset()
2345+
2346+
key := "foo"
2347+
SetType(key, []string{})
2348+
Set(key, "bar")
2349+
assert.Equal(t, []string{"bar"}, Get(key))
2350+
})
2351+
2352+
t.Run("case=converts string to string slice length 3", func(t *testing.T) {
2353+
Reset()
2354+
2355+
key := "key"
2356+
SetType(key, []string{})
2357+
Set(key, "a b c")
2358+
assert.Equal(t, []string{"a", "b", "c"}, Get(key))
2359+
})
2360+
2361+
t.Run("case=SetType has precedence over default", func(t *testing.T) {
2362+
Reset()
2363+
2364+
key := "test_key"
2365+
SetTypeByDefaultValue(true)
2366+
SetDefault(key, 1)
2367+
SetType(key, []string{})
2368+
Set(key, "foo bar")
2369+
assert.Equal(t, []string{"foo", "bar"}, Get(key))
2370+
})
2371+
2372+
t.Run("case=type conversion from default value works aswell", func(t *testing.T) {
2373+
Reset()
2374+
2375+
key := "key"
2376+
SetDefault(key, []string{})
2377+
SetTypeByDefaultValue(true)
2378+
Set(key, "a b c")
2379+
assert.Equal(t, []string{"a", "b", "c"}, Get(key))
2380+
})
2381+
}
2382+
23412383
func TestRace(t *testing.T) {
23422384
Reset()
23432385

0 commit comments

Comments
 (0)