package viper import ( "strings" "testing" "github.com/spf13/cast" "github.com/stretchr/testify/assert" ) type layer int const ( defaultLayer layer = iota + 1 overrideLayer ) func TestNestedOverrides(t *testing.T) { assert := assert.New(t) var v *Viper // Case 0: value overridden by a value overrideDefault(assert, "tom", 10, "tom", 20) // "tom" is first given 10 as default value, then overridden by 20 override(assert, "tom", 10, "tom", 20) // "tom" is first given value 10, then overridden by 20 overrideDefault(assert, "tom.age", 10, "tom.age", 20) override(assert, "tom.age", 10, "tom.age", 20) overrideDefault(assert, "sawyer.tom.age", 10, "sawyer.tom.age", 20) override(assert, "sawyer.tom.age", 10, "sawyer.tom.age", 20) // Case 1: key:value overridden by a value v = overrideDefault(assert, "tom.age", 10, "tom", "boy") // "tom.age" is first given 10 as default value, then "tom" is overridden by "boy" assert.Nil(v.Get("tom.age")) // "tom.age" should not exist anymore v = override(assert, "tom.age", 10, "tom", "boy") assert.Nil(v.Get("tom.age")) // Case 2: value overridden by a key:value overrideDefault(assert, "tom", "boy", "tom.age", 10) // "tom" is first given "boy" as default value, then "tom" is overridden by map{"age":10} override(assert, "tom.age", 10, "tom", "boy") // Case 3: key:value overridden by a key:value v = overrideDefault(assert, "tom.size", 4, "tom.age", 10) assert.Equal(4, v.Get("tom.size")) // value should still be reachable v = override(assert, "tom.size", 4, "tom.age", 10) assert.Equal(4, v.Get("tom.size")) deepCheckValue(assert, v, overrideLayer, []string{"tom", "size"}, 4) // Case 4: key:value overridden by a map v = overrideDefault(assert, "tom.size", 4, "tom", map[string]any{"age": 10}) // "tom.size" is first given "4" as default value, then "tom" is overridden by map{"age":10} assert.Equal(4, v.Get("tom.size")) // "tom.size" should still be reachable assert.Equal(10, v.Get("tom.age")) // new value should be there deepCheckValue(assert, v, overrideLayer, []string{"tom", "age"}, 10) // new value should be there v = override(assert, "tom.size", 4, "tom", map[string]any{"age": 10}) assert.Nil(v.Get("tom.size")) assert.Equal(10, v.Get("tom.age")) deepCheckValue(assert, v, overrideLayer, []string{"tom", "age"}, 10) // Case 5: array overridden by a value overrideDefault(assert, "tom", []int{10, 20}, "tom", 30) override(assert, "tom", []int{10, 20}, "tom", 30) overrideDefault(assert, "tom.age", []int{10, 20}, "tom.age", 30) override(assert, "tom.age", []int{10, 20}, "tom.age", 30) // Case 6: array overridden by an array overrideDefault(assert, "tom", []int{10, 20}, "tom", []int{30, 40}) override(assert, "tom", []int{10, 20}, "tom", []int{30, 40}) overrideDefault(assert, "tom.age", []int{10, 20}, "tom.age", []int{30, 40}) v = override(assert, "tom.age", []int{10, 20}, "tom.age", []int{30, 40}) // explicit array merge: s, ok := v.Get("tom.age").([]int) if assert.True(ok, "tom[\"age\"] is not a slice") { v.Set("tom.age", append(s, []int{50, 60}...)) assert.Equal([]int{30, 40, 50, 60}, v.Get("tom.age")) deepCheckValue(assert, v, overrideLayer, []string{"tom", "age"}, []int{30, 40, 50, 60}) } } func overrideDefault(assert *assert.Assertions, firstPath string, firstValue any, secondPath string, secondValue any) *Viper { return overrideFromLayer(defaultLayer, assert, firstPath, firstValue, secondPath, secondValue) } func override(assert *assert.Assertions, firstPath string, firstValue any, secondPath string, secondValue any) *Viper { return overrideFromLayer(overrideLayer, assert, firstPath, firstValue, secondPath, secondValue) } // overrideFromLayer performs the sequential override and low-level checks. // // First assignment is made on layer l for path firstPath with value firstValue, // the second one on the override layer (i.e., with the Set() function) // for path secondPath with value secondValue. // // firstPath and secondPath can include an arbitrary number of dots to indicate // a nested element. // // After each assignment, the value is checked, retrieved both by its full path // and by its key sequence (successive maps). func overrideFromLayer(l layer, assert *assert.Assertions, firstPath string, firstValue any, secondPath string, secondValue any) *Viper { v := New() firstKeys := strings.Split(firstPath, v.keyDelim) if assert == nil || len(firstKeys) == 0 || len(firstKeys[0]) == 0 { return v } // Set and check first value switch l { case defaultLayer: v.SetDefault(firstPath, firstValue) case overrideLayer: v.Set(firstPath, firstValue) default: return v } assert.Equal(firstValue, v.Get(firstPath)) deepCheckValue(assert, v, l, firstKeys, firstValue) // Override and check new value secondKeys := strings.Split(secondPath, v.keyDelim) if len(secondKeys) == 0 || len(secondKeys[0]) == 0 { return v } v.Set(secondPath, secondValue) assert.Equal(secondValue, v.Get(secondPath)) deepCheckValue(assert, v, overrideLayer, secondKeys, secondValue) return v } // deepCheckValue checks that all given keys correspond to a valid path in the // configuration map of the given layer, and that the final value equals the one given. func deepCheckValue(assert *assert.Assertions, v *Viper, l layer, keys []string, value any) { if assert == nil || v == nil || len(keys) == 0 || len(keys[0]) == 0 { return } // init var val any var ms string switch l { case defaultLayer: val = v.defaults ms = "v.defaults" case overrideLayer: val = v.override ms = "v.override" } // loop through map var m map[string]any for _, k := range keys { if val == nil { assert.Failf("%s is not a map[string]any", ms) return } // deep scan of the map to get the final value switch val := val.(type) { case map[any]any: m = cast.ToStringMap(val) case map[string]any: m = val default: assert.Failf("%s is not a map[string]any", ms) return } ms = ms + "[\"" + k + "\"]" val = m[k] } assert.Equal(value, val) }