Upstream knadh/koanf state of the touched files before e3921078a3

This commit is contained in:
upstream committed 2026-09-04 08:08:58 +02:00
1 parent 219bbcd219
commit 15e76cb3d5
2 files changed
+21 -197

No files matched your search

+21 -28
View File
@@ -368,26 +368,29 @@ func (ko *Koanf) Get(path string) any {
return out
}
// Slices returns a list of Koanf instances constructed from the value
// at path. It accepts []map[string]any or []any containing map[string]any
// elements. Non-slice elements are skipped.
// Slices returns a list of Koanf instances constructed out of a
// []map[string]any interface at the given path.
func (ko *Koanf) Slices(path string) []*Koanf {
out := []*Koanf{}
if path == "" {
return out
}
switch v := ko.Get(path).(type) {
case []map[string]any:
for _, mp := range v {
out = ko.appendMap(mp, out)
}
case []any:
for _, item := range v {
if mp, ok := item.(map[string]any); ok {
out = ko.appendMap(mp, out)
}
// Does the path exist?
sl, ok := ko.Get(path).([]any)
if !ok {
return out
}
for _, s := range sl {
mp, ok := s.(map[string]any)
if !ok {
continue
}
k := New(ko.conf.Delim)
_ = k.merge(mp, new(options))
out = append(out, k)
}
return out
@@ -413,16 +416,13 @@ func (ko *Koanf) MapKeys(path string) []string {
return out
}
// A value set with Set() or by a Provider such as structs keeps its native
// map type, so the keys have to be read through reflection too.
rv := reflect.ValueOf(o)
if rv.Kind() != reflect.Map || rv.Type().Key().Kind() != reflect.String {
mp, ok := o.(map[string]any)
if !ok {
return out
}
out = make([]string, 0, rv.Len())
for _, k := range rv.MapKeys() {
out = append(out, k.String())
out = make([]string, 0, len(mp))
for k := range mp {
out = append(out, k)
}
sort.Strings(out)
return out
@@ -656,10 +656,3 @@ func textUnmarshalerHookFunc() mapstructure.DecodeHookFuncType {
return result, nil
}
}
// appendMap creates new Koanf instances from a map returns a slice of Koanf instances.
func (ko *Koanf) appendMap(mp map[string]any, out []*Koanf) []*Koanf {
k := New(ko.conf.Delim)
_ = k.merge(mp, new(options))
return append(out, k)
}
-169
View File
@@ -1307,90 +1307,6 @@ func TestSlices(t *testing.T) {
}
}
func TestSlicesNativeMapSlice(t *testing.T) {
assert := assert.New(t)
// A natively-typed []map[string]any (as produced by the confmap and
// structs providers, and named in Slices' own doc) must be handled too,
// not just a []any of maps decoded by a parser. It was silently dropped.
k := koanf.New(delim)
require.NoError(t, k.Load(confmap.Provider(map[string]any{
"servers": []map[string]any{
{"host": "a", "port": 1},
{"host": "b", "port": 2},
},
}, "."), nil))
slices := k.Slices("servers")
require.Len(t, slices, 2, "Slices dropped a []map[string]any value")
assert.Equal("a", slices[0].String("host"))
assert.Equal(1, slices[0].Int("port"))
assert.Equal("b", slices[1].String("host"))
assert.Equal(2, slices[1].Int("port"))
}
func TestSlicesEdgeCases(t *testing.T) {
for _, tt := range []struct {
name string
value any
want []map[string]any
}{
{"nil_native_slice", []map[string]any(nil), []map[string]any{}},
{"empty_native_slice", []map[string]any{}, []map[string]any{}},
{"nil_interface_slice", []any(nil), []map[string]any{}},
{"empty_interface_slice", []any{}, []map[string]any{}},
{"nil_and_empty_native_maps", []map[string]any{nil, {}}, []map[string]any{{}, {}}},
{"nil_and_empty_interface_maps", []any{map[string]any(nil), map[string]any{}}, []map[string]any{{}, {}}},
{
"mixed_interface_slice",
[]any{nil, 42, map[string]any{"host": "a"}, "invalid", map[string]any{"host": "b"}},
[]map[string]any{{"host": "a"}, {"host": "b"}},
},
} {
t.Run(tt.name, func(t *testing.T) {
k := koanf.New(delim)
require.NoError(t, k.Load(confmap.Provider(map[string]any{"servers": tt.value}, ""), nil))
slices := k.Slices("servers")
require.NotNil(t, slices)
require.Len(t, slices, len(tt.want))
for i, child := range slices {
assert.Equal(t, tt.want[i], child.Raw())
}
})
}
}
func TestSlicesCopyIsolation(t *testing.T) {
for _, native := range []bool{false, true} {
name := "interface_slice"
if native {
name = "native_slice"
}
t.Run(name, func(t *testing.T) {
// Reuse the same map to check that siblings are independent too.
mp := map[string]any{"nested": map[string]any{"host": "original"}}
var value any = []any{mp, mp}
if native {
value = []map[string]any{mp, mp}
}
k := koanf.New("/")
require.NoError(t, k.Load(confmap.Provider(map[string]any{"servers": value}, ""), nil))
before := k.Get("servers")
slices := k.Slices("servers")
require.Len(t, slices, 2)
assert.Equal(t, "/", slices[0].Delim())
assert.Equal(t, "original", slices[0].String("nested/host"))
require.NoError(t, slices[0].Set("nested/host", "changed"))
assert.Equal(t, "changed", slices[0].String("nested/host"))
assert.Equal(t, "original", slices[1].String("nested/host"))
assert.Equal(t, before, k.Get("servers"))
})
}
}
func TestGetTypes(t *testing.T) {
assert := assert.New(t)
for _, c := range cases {
@@ -1606,38 +1522,6 @@ func TestGetStringsMap(t *testing.T) {
assert.Equal(map[string][]string{"k2": {"value"}}, k.StringsMap("ifaces3"), "types don't match")
}
func TestGetStringsMapEmptySlices(t *testing.T) {
for _, tt := range []struct {
name string
value any
}{
{"string_slices", map[string][]string{"empty": {}, "nil": nil}},
{"interface_slices", map[string][]any{"empty": {}, "nil": nil}},
{"interface_map", map[string]any{"empty": []any{}, "nil": []any(nil)}},
} {
t.Run(tt.name, func(t *testing.T) {
k := koanf.New(delim)
require.NoError(t, k.Set("lists", tt.value))
want := map[string][]string{"empty": nil, "nil": nil}
require.Equal(t, want, k.StringsMap("lists"))
require.Equal(t, want, k.MustStringsMap("lists"))
})
}
t.Run("json", func(t *testing.T) {
k := koanf.New(delim)
require.NoError(t, k.Load(rawbytes.Provider([]byte(`{"lists":{"empty":[],"populated":["value"]}}`)), json.Parser()))
require.Equal(t, map[string][]string{"empty": nil, "populated": {"value"}}, k.StringsMap("lists"))
})
t.Run("invalid_element", func(t *testing.T) {
k := koanf.New(delim)
require.NoError(t, k.Set("lists", map[string][]any{"empty": {}, "invalid": {42}}))
require.Empty(t, k.StringsMap("lists"))
require.Panics(t, func() { k.MustStringsMap("lists") })
})
}
func TestBoolsNativeSlice(t *testing.T) {
assert := assert.New(t)
@@ -1649,59 +1533,6 @@ func TestBoolsNativeSlice(t *testing.T) {
assert.Equal([]bool{true, false, true}, k.Bools("bools"))
}
func TestTypedGettersNativeMaps(t *testing.T) {
for _, tt := range []struct {
name string
value any
get func(*koanf.Koanf, string) any
}{
{"int", map[string]int{"a": 1, "b": 2}, func(k *koanf.Koanf, path string) any { return k.IntMap(path) }},
{"int64", map[string]int64{"a": 1 << 54, "b": -2}, func(k *koanf.Koanf, path string) any { return k.Int64Map(path) }},
{"float64", map[string]float64{"a": 1.5, "b": -2.5}, func(k *koanf.Koanf, path string) any { return k.Float64Map(path) }},
{"bool", map[string]bool{"a": true, "b": false}, func(k *koanf.Koanf, path string) any { return k.BoolMap(path) }},
{"nil_int", map[string]int(nil), func(k *koanf.Koanf, path string) any { return k.IntMap(path) }},
{"nil_int64", map[string]int64(nil), func(k *koanf.Koanf, path string) any { return k.Int64Map(path) }},
{"nil_float64", map[string]float64(nil), func(k *koanf.Koanf, path string) any { return k.Float64Map(path) }},
{"nil_bool", map[string]bool(nil), func(k *koanf.Koanf, path string) any { return k.BoolMap(path) }},
} {
t.Run(tt.name, func(t *testing.T) {
k := koanf.New(delim)
require.NoError(t, k.Load(confmap.Provider(map[string]any{"value": tt.value}, ""), nil))
got := tt.get(k, "value")
require.NotNil(t, got)
if strings.HasPrefix(tt.name, "nil_") {
assert.Empty(t, got)
return
}
assert.Equal(t, tt.value, got)
// Changing the returned map must not change the stored configuration.
switch mp := got.(type) {
case map[string]int:
mp["a"] = 0
case map[string]int64:
mp["a"] = 0
case map[string]float64:
mp["a"] = 0
case map[string]bool:
mp["a"] = false
}
assert.Equal(t, tt.value, tt.get(k, "value"))
})
}
}
func TestMapKeysNativeMap(t *testing.T) {
assert := assert.New(t)
k := koanf.New(delim)
assert.NoError(k.Load(confmap.Provider(map[string]any{
"ints": map[string]int64{"a": 1, "b": 2},
}, "."), nil))
assert.Equal([]string{"a", "b"}, k.MapKeys("ints"), "map keys mismatch for a natively-typed map")
}
// waitTimeout waits for the waitgroup for the specified max timeout.
// Returns true if waiting timed out.
func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {