Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
29 changes: 16 additions & 13 deletions viper.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,6 @@ func (v *Viper) searchIndexableWithPathPrefixes(source any, path []string) any {
// 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))

var val any
switch sourceIndexable := source.(type) {
case []any:
Expand Down Expand Up @@ -593,7 +592,7 @@ func (v *Viper) isPathShadowedInDeepMap(path []string, m map[string]any) string
continue
default:
// parentVal is a regular value which shadows "path"
return strings.Join(path[0:i], v.keyDelim)
return strings.Join(path[0:i], strings.ToLower(v.keyDelim))
}
}
return ""
Expand All @@ -619,7 +618,7 @@ func (v *Viper) isPathShadowedInFlatMap(path []string, mi any) string {
// scan paths
var parentKey string
for i := 1; i < len(path); i++ {
parentKey = strings.Join(path[0:i], v.keyDelim)
parentKey = strings.Join(path[0:i], strings.ToLower(v.keyDelim))
if _, ok := m[parentKey]; ok {
return parentKey
}
Expand All @@ -635,7 +634,7 @@ func (v *Viper) isPathShadowedInFlatMap(path []string, mi any) string {
func (v *Viper) isPathShadowedInAutoEnv(path []string) string {
var parentKey string
for i := 1; i < len(path); i++ {
parentKey = strings.Join(path[0:i], v.keyDelim)
parentKey = strings.Join(path[0:i], strings.ToLower(v.keyDelim))
if _, ok := v.getEnv(v.mergeWithEnvPrefix(parentKey)); ok {
return parentKey
}
Expand Down Expand Up @@ -687,7 +686,7 @@ func (v *Viper) Get(key string) any {
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(lcaseKey, strings.ToLower(v.keyDelim))
defVal := v.searchMap(v.defaults, path)
if defVal != nil {
valType = defVal
Expand Down Expand Up @@ -1114,7 +1113,7 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) any {
var (
val any
exists bool
path = strings.Split(lcaseKey, v.keyDelim)
path = strings.Split(lcaseKey, strings.ToLower(v.keyDelim))
nested = len(path) > 1
)

Expand All @@ -1125,7 +1124,7 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) any {

// if the requested key is an alias, then return the proper key
lcaseKey = v.realKey(lcaseKey)
path = strings.Split(lcaseKey, v.keyDelim)
path = strings.Split(lcaseKey, strings.ToLower(v.keyDelim))
nested = len(path) > 1

// Set() override first
Expand Down Expand Up @@ -1433,7 +1432,7 @@ func (v *Viper) InConfig(key string) bool {

// if the requested key is an alias, then return the proper key
lcaseKey = v.realKey(lcaseKey)
path := strings.Split(lcaseKey, v.keyDelim)
path := strings.Split(lcaseKey, strings.ToLower(v.keyDelim))

return v.searchIndexableWithPathPrefixes(v.config, path) != nil
}
Expand All @@ -1448,7 +1447,7 @@ func (v *Viper) SetDefault(key string, value any) {
key = v.realKey(strings.ToLower(key))
value = toCaseInsensitiveValue(value)

path := strings.Split(key, v.keyDelim)
path := strings.Split(key, strings.ToLower(v.keyDelim))
lastKey := strings.ToLower(path[len(path)-1])
deepestMap := deepSearch(v.defaults, path[0:len(path)-1])

Expand All @@ -1464,10 +1463,12 @@ func Set(key string, value any) { v.Set(key, value) }

func (v *Viper) Set(key string, value any) {
// If alias passed in, then set the proper override

key = v.realKey(strings.ToLower(key))
value = toCaseInsensitiveValue(value)

path := strings.Split(key, v.keyDelim)
path := strings.Split(key, strings.ToLower(v.keyDelim))
Comment thread
maxBRT marked this conversation as resolved.
Outdated

lastKey := strings.ToLower(path[len(path)-1])
deepestMap := deepSearch(v.override, path[0:len(path)-1])

Expand Down Expand Up @@ -1917,11 +1918,13 @@ func (v *Viper) mergeFlatMap(shadow map[string]bool, m map[string]any) map[strin
// scan keys
outer:
for k := range m {
path := strings.Split(k, v.keyDelim)
fmt.Println(k)
Comment thread
maxBRT marked this conversation as resolved.
Outdated
path := strings.Split(k, strings.ToLower(v.keyDelim))
fmt.Println(path)
// scan intermediate paths
var parentKey string
for i := 1; i < len(path); i++ {
parentKey = strings.Join(path[0:i], v.keyDelim)
parentKey = strings.Join(path[0:i], strings.ToLower(v.keyDelim))
Comment thread
maxBRT marked this conversation as resolved.
Outdated
if shadow[parentKey] {
// path is shadowed, continue
continue outer
Expand Down Expand Up @@ -1950,7 +1953,7 @@ func (v *Viper) getSettings(keys []string) map[string]any {
// check just in case anything changes
continue
}
path := strings.Split(k, v.keyDelim)
path := strings.Split(k, strings.ToLower(v.keyDelim))
lastKey := strings.ToLower(path[len(path)-1])
deepestMap := deepSearch(m, path[0:len(path)-1])
// set innermost value
Expand Down
148 changes: 115 additions & 33 deletions viper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2533,50 +2533,132 @@ func TestUnmarshal_DotSeparatorBackwardCompatibility(t *testing.T) {
// `)

func TestKeyDelimiter(t *testing.T) {
v := NewWithOptions(KeyDelimiter("::"))
v.SetConfigType("yaml")
r := strings.NewReader(string(yamlExampleWithDot))
t.Run("KeyDelimiterYAMLAndUnmarshal", func(t *testing.T) {
Comment thread
maxBRT marked this conversation as resolved.
v := NewWithOptions(KeyDelimiter("::"))
v.SetConfigType("yaml")
r := strings.NewReader(string(yamlExampleWithDot))

err := v.unmarshalReader(r, v.config)
require.NoError(t, err)
err := v.unmarshalReader(r, v.config)
require.NoError(t, err)

values := map[string]any{
"image": map[string]any{
"repository": "someImage",
"tag": "1.0.0",
},
"ingress": map[string]any{
"annotations": map[string]any{
"traefik.frontend.rule.type": "PathPrefix",
"traefik.ingress.kubernetes.io/ssl-redirect": "true",
values := map[string]any{
"image": map[string]any{
"repository": "someImage",
"tag": "1.0.0",
},
},
}
"ingress": map[string]any{
"annotations": map[string]any{
"traefik.frontend.rule.type": "PathPrefix",
"traefik.ingress.kubernetes.io/ssl-redirect": "true",
},
},
}

v.SetDefault("charts::values", values)
v.SetDefault("charts::values", values)

assert.Equal(t, "leather", v.GetString("clothing::jacket"))
assert.Equal(t, "01/02/03", v.GetString("emails::steve@hacker.com::created"))
assert.Equal(t, "leather", v.GetString("clothing::jacket"))
Comment thread
maxBRT marked this conversation as resolved.
assert.Equal(t, "01/02/03", v.GetString("emails::steve@hacker.com::created"))

type config struct {
Charts struct {
Values map[string]any
type config struct {
Charts struct {
Values map[string]any
}
}
}

expected := config{
Charts: struct {
Values map[string]any
}{
Values: values,
},
}
expected := config{
Charts: struct {
Values map[string]any
}{
Values: values,
},
}

var actual config
var actual config

require.NoError(t, v.Unmarshal(&actual))
require.NoError(t, v.Unmarshal(&actual))

assert.Equal(t, expected, actual)
assert.Equal(t, expected, actual)
Comment thread
maxBRT marked this conversation as resolved.
})

// Test the Set method with key delimiter for case insenitivty
t.Run("CaseInsensitiveDelimiter", func(t *testing.T) {
v := NewWithOptions(KeyDelimiter("Z"))
v.Set("fooZbar", "Foo Bar Baz")

got := v.Get("foo")
want := map[string]any{"bar": "Foo Bar Baz"}
assert.Equal(t, want, got)

v.Set("foozbar", "Foo Bar Baz")
got = v.Get("foo")
assert.Equal(t, want, got)

v.Set("baz", "Bazzz")
Comment thread
maxBRT marked this conversation as resolved.
got = v.Get("baz")
want2 := "Bazzz"
assert.Equal(t, want2, got)
})

// Test the InConfig method with key delimiter for case insenitivty
t.Run("UpperCasedDelimInConfig", func(t *testing.T) {
v := NewWithOptions(KeyDelimiter("Z"))

v.config = map[string]any{
"foo": map[string]any{
"bar": "nestedValue",
},
}
assert.True(t, v.InConfig("fooZbar"))
assert.True(t, v.InConfig("foozbar"))
})

// Test the SetDefault method with key delimiter for case insenitivty
t.Run("UpperCasedDelimSetDefault", func(t *testing.T) {
v := NewWithOptions(KeyDelimiter("Z"))
v.SetDefault("fooZbar", "Foo Bar Baz")

got := v.Get("foo")
want := map[string]any{"bar": "Foo Bar Baz"}
assert.Equal(t, want, got)

v.SetDefault("foozbar", "Foo Bar Baz")
got = v.Get("foo")
assert.Equal(t, want, got)
})

// Test the flattenAndMergeMap private method with key delimiter for case insenitivty
t.Run("UpperCasedDelimFlattenAndMerge", func(t *testing.T) {
config := map[string]any{
"foo": map[string]any{
"bar": 123,
"baz": 456,
},
}

v := NewWithOptions(KeyDelimiter("Z"))
shadow := make(map[string]bool)
shadow = v.flattenAndMergeMap(shadow, config, "")

assert.True(t, shadow["foozbar"])
Comment thread
maxBRT marked this conversation as resolved.
Outdated
assert.True(t, shadow["foozbaz"])
})

// Test the AllSettings method with key delimiter for case insenitivty
t.Run("UpperCasedDelimAllSettings", func(t *testing.T) {
v := NewWithOptions(KeyDelimiter("Z"))

v.Set("foozbar", 123)

got := v.AllSettings()

want := map[string]any{
"foo": map[string]any{
"bar": 123,
},
}

assert.Equal(t, want, got)
})
}

var yamlDeepNestedSlices = []byte(`TV:
Expand Down