diff --git a/lib/decode/decode.go b/lib/decode/decode.go index b098e43135..26eab31a5b 100644 --- a/lib/decode/decode.go +++ b/lib/decode/decode.go @@ -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 } diff --git a/lib/decode/decode_test.go b/lib/decode/decode_test.go index cc2ca1bfb0..2fcf5448a5 100644 --- a/lib/decode/decode_test.go +++ b/lib/decode/decode_test.go @@ -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) +}