decode: recursively unslice opaque config

Also handle []interface{} in HookWeakDecodeFromSlice

Without this change only the top level []map[string]interface{} will be
unpacked as a single item. With this change any nested config will be
unpacked.
This commit is contained in:
Daniel Nephin 2020-06-12 17:14:00 -04:00
parent 66e2def461
commit f613c919d2
2 changed files with 71 additions and 9 deletions

View File

@ -7,6 +7,8 @@ package decode
import (
"reflect"
"strings"
"github.com/mitchellh/reflectwalk"
)
// HookTranslateKeys is a mapstructure decode hook which translates keys in a
@ -120,15 +122,39 @@ func HookWeakDecodeFromSlice(from, to reflect.Type, data interface{}) (interface
switch d := data.(type) {
case []map[string]interface{}:
switch {
case len(d) == 0:
return nil, nil
case len(d) == 1:
return d[0], nil
default:
return data, nil
if len(d) == 1 {
return unSlice(d[0])
}
// the JSON decoder can apparently decode slices as []interface{}
case []interface{}:
if len(d) == 1 {
return unSlice(d[0])
}
default:
return data, nil
}
return data, nil
}
func unSlice(data interface{}) (interface{}, error) {
err := reflectwalk.Walk(data, &unSliceWalker{})
return data, err
}
type unSliceWalker struct{}
func (u *unSliceWalker) Map(_ reflect.Value) error {
return nil
}
func (u *unSliceWalker) MapElem(m, k, v reflect.Value) error {
if !v.IsValid() || v.Kind() != reflect.Interface {
return nil
}
v = v.Elem() // unpack the value from the interface{}
if v.Kind() != reflect.Slice || v.Len() != 1 {
return nil
}
m.SetMapIndex(k, v.Index(0))
return nil
}

View File

@ -272,3 +272,39 @@ item {
}
require.Equal(t, target, expected)
}
func TestHookWeakDecodeFromSlice_NestedOpaqueConfig(t *testing.T) {
source := `
service {
proxy {
config {
envoy_gateway_bind_addresses {
all-interfaces {
address = "0.0.0.0"
port = 8443
}
}
}
}
}`
target := map[string]interface{}{}
err := decodeHCLToMapStructure(source, &target)
require.NoError(t, err)
expected := map[string]interface{}{
"service": map[string]interface{}{
"proxy": map[string]interface{}{
"config": map[string]interface{}{
"envoy_gateway_bind_addresses": map[string]interface{}{
"all-interfaces": map[string]interface{}{
"address": "0.0.0.0",
"port": 8443,
},
},
},
},
},
}
require.Equal(t, target, expected)
}