mirror of
https://github.com/spf13/viper
synced 2025-05-07 20:57:18 +00:00
New merge strategy feature
- Added to allow the possability of building strategies for conf merging - One implented for slice appending
This commit is contained in:
parent
6d33b5a963
commit
5a972e97cf
2 changed files with 125 additions and 14 deletions
79
viper.go
79
viper.go
|
@ -131,6 +131,45 @@ func DecodeHook(hook mapstructure.DecodeHookFunc) DecoderConfigOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MergeStrategy interface {
|
||||||
|
Can(interface{}) bool
|
||||||
|
Merge(src, tgt interface{}) interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mergeStrategy struct {
|
||||||
|
CanFn func(interface{}) bool
|
||||||
|
MergeFn func(src, tgt interface{}) interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mergeStrategy) Can(val interface{}) bool {
|
||||||
|
return m.CanFn(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mergeStrategy) Merge(src, tgt interface{}) interface{} {
|
||||||
|
return m.MergeFn(src, tgt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMergeStrategy(canFn func(interface{}) bool,
|
||||||
|
mergeFn func(src, tgt interface{}) interface{}) *mergeStrategy {
|
||||||
|
return &mergeStrategy{
|
||||||
|
CanFn: canFn,
|
||||||
|
MergeFn: mergeFn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SliceAppendStrategy() MergeStrategy {
|
||||||
|
return newMergeStrategy(func(i interface{}) bool {
|
||||||
|
val := reflect.ValueOf(i)
|
||||||
|
if val.Kind() != reflect.Slice {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}, func(src, tgt interface{}) interface{} {
|
||||||
|
return reflect.AppendSlice(reflect.ValueOf(tgt), reflect.ValueOf(src)).
|
||||||
|
Interface()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Viper is a prioritized configuration registry. It
|
// Viper is a prioritized configuration registry. It
|
||||||
// maintains a set of configuration sources, fetches
|
// maintains a set of configuration sources, fetches
|
||||||
// values to populate those, and provides them according
|
// values to populate those, and provides them according
|
||||||
|
@ -189,14 +228,15 @@ type Viper struct {
|
||||||
envKeyReplacer *strings.Replacer
|
envKeyReplacer *strings.Replacer
|
||||||
allowEmptyEnv bool
|
allowEmptyEnv bool
|
||||||
|
|
||||||
config map[string]interface{}
|
config map[string]interface{}
|
||||||
override map[string]interface{}
|
override map[string]interface{}
|
||||||
defaults map[string]interface{}
|
defaults map[string]interface{}
|
||||||
kvstore map[string]interface{}
|
kvstore map[string]interface{}
|
||||||
pflags map[string]FlagValue
|
pflags map[string]FlagValue
|
||||||
env map[string]string
|
env map[string]string
|
||||||
aliases map[string]string
|
aliases map[string]string
|
||||||
typeByDefValue bool
|
typeByDefValue bool
|
||||||
|
mergeStrategies []MergeStrategy
|
||||||
|
|
||||||
// Store read properties on the object so that we can write back in order with comments.
|
// Store read properties on the object so that we can write back in order with comments.
|
||||||
// This will only be used if the configuration read is a properties file.
|
// This will only be used if the configuration read is a properties file.
|
||||||
|
@ -1274,7 +1314,7 @@ func (v *Viper) MergeConfigMap(cfg map[string]interface{}) error {
|
||||||
v.config = make(map[string]interface{})
|
v.config = make(map[string]interface{})
|
||||||
}
|
}
|
||||||
insensitiviseMap(cfg)
|
insensitiviseMap(cfg)
|
||||||
mergeMaps(cfg, v.config, nil)
|
mergeMaps(cfg, v.config, nil, v.mergeStrategies)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1509,7 +1549,8 @@ func castMapFlagToMapInterface(src map[string]FlagValue) map[string]interface{}
|
||||||
// deep. Both map types are supported as there is a go-yaml fork that uses
|
// deep. Both map types are supported as there is a go-yaml fork that uses
|
||||||
// `map[string]interface{}` instead.
|
// `map[string]interface{}` instead.
|
||||||
func mergeMaps(
|
func mergeMaps(
|
||||||
src, tgt map[string]interface{}, itgt map[interface{}]interface{}) {
|
src, tgt map[string]interface{}, itgt map[interface{}]interface{},
|
||||||
|
strategies []MergeStrategy) {
|
||||||
for sk, sv := range src {
|
for sk, sv := range src {
|
||||||
tk := keyExists(sk, tgt)
|
tk := keyExists(sk, tgt)
|
||||||
if tk == "" {
|
if tk == "" {
|
||||||
|
@ -1549,15 +1590,25 @@ func mergeMaps(
|
||||||
tsv := sv.(map[interface{}]interface{})
|
tsv := sv.(map[interface{}]interface{})
|
||||||
ssv := castToMapStringInterface(tsv)
|
ssv := castToMapStringInterface(tsv)
|
||||||
stv := castToMapStringInterface(ttv)
|
stv := castToMapStringInterface(ttv)
|
||||||
mergeMaps(ssv, stv, ttv)
|
mergeMaps(ssv, stv, ttv, strategies)
|
||||||
|
continue
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
jww.TRACE.Printf("merging maps")
|
jww.TRACE.Printf("merging maps")
|
||||||
mergeMaps(sv.(map[string]interface{}), ttv, nil)
|
mergeMaps(sv.(map[string]interface{}), ttv, nil, strategies)
|
||||||
|
continue
|
||||||
default:
|
default:
|
||||||
|
var val interface{} = sv
|
||||||
|
for _, strat := range strategies {
|
||||||
|
if !strat.Can(tv) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val = strat.Merge(sv, tv)
|
||||||
|
break
|
||||||
|
}
|
||||||
jww.TRACE.Printf("setting value")
|
jww.TRACE.Printf("setting value")
|
||||||
tgt[tk] = sv
|
tgt[tk] = val
|
||||||
if itgt != nil {
|
if itgt != nil {
|
||||||
itgt[tk] = sv
|
itgt[tk] = val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1272,6 +1272,66 @@ func TestMergeConfigMap(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMergeMapsSliceWithoutStategy(t *testing.T) {
|
||||||
|
srcSlc := []string{"one", "two", "three"}
|
||||||
|
tgtSlc := []string{"four", "five", "six"}
|
||||||
|
src := map[string]interface{}{
|
||||||
|
"testKey": srcSlc,
|
||||||
|
}
|
||||||
|
tgt := map[string]interface{}{
|
||||||
|
"testKey": tgtSlc,
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeMaps(src, tgt, nil, []MergeStrategy{})
|
||||||
|
val, ok := tgt["testKey"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("unable to get key in merged target for map")
|
||||||
|
}
|
||||||
|
if val, ok := val.([]string); !ok {
|
||||||
|
t.Fatal("unexpected type in merged target for test key")
|
||||||
|
} else if !reflect.DeepEqual(val, srcSlc) {
|
||||||
|
t.Fatalf("unexpected key value, wanted %s got %s", srcSlc, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeMapsSliceWithStategy(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
srcSlc []interface{}
|
||||||
|
tgtSlc []interface{}
|
||||||
|
result []interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
srcSlc: []interface{}{"one", "two", "three"},
|
||||||
|
tgtSlc: []interface{}{"four", "five", "six"},
|
||||||
|
result: []interface{}{"four", "five", "six", "one", "two", "three"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
srcSlc: []interface{}{1, 2, 3},
|
||||||
|
tgtSlc: []interface{}{4, 5, 6},
|
||||||
|
result: []interface{}{4, 5, 6, 1, 2, 3},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
src := map[string]interface{}{
|
||||||
|
"testKey": test.srcSlc,
|
||||||
|
}
|
||||||
|
tgt := map[string]interface{}{
|
||||||
|
"testKey": test.tgtSlc,
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeMaps(src, tgt, nil, []MergeStrategy{SliceAppendStrategy()})
|
||||||
|
val, ok := tgt["testKey"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("unable to get key in merged target for map")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(val, test.result) {
|
||||||
|
t.Fatalf("unexpected key value, wanted %s got %s", test.result,
|
||||||
|
val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnmarshalingWithAliases(t *testing.T) {
|
func TestUnmarshalingWithAliases(t *testing.T) {
|
||||||
v := New()
|
v := New()
|
||||||
v.SetDefault("ID", 1)
|
v.SetDefault("ID", 1)
|
||||||
|
|
Loading…
Add table
Reference in a new issue