diff --git a/values/convert.go b/values/convert.go index cc1c870..bbe923d 100644 --- a/values/convert.go +++ b/values/convert.go @@ -70,6 +70,34 @@ func Convert(value interface{}, typ reflect.Type) (interface{}, error) { // noli case reflect.Map: et := typ.Elem() result := reflect.MakeMap(typ) + if ms, ok := value.(yaml.MapSlice); ok { + for _, item := range ms { + var k, v reflect.Value + if item.Key == nil { + k = reflect.Zero(typ.Key()) + } else { + kc, err := Convert(item.Key, typ.Key()) + if err != nil { + return nil, err + } + k = reflect.ValueOf(kc) + } + if item.Value == nil { + v = reflect.Zero(et) + } else { + ec, err := Convert(item.Value, et) + if err != nil { + return nil, err + } + v = reflect.ValueOf(ec) + } + result.SetMapIndex(k, v) + } + return result.Interface(), nil + } + if rv.Kind() != reflect.Map { + return nil, conversionError("", value, typ) + } for _, key := range rv.MapKeys() { if typ.Key().Kind() == reflect.String { key = reflect.ValueOf(fmt.Sprint(key)) @@ -83,7 +111,7 @@ func Convert(value interface{}, typ reflect.Type) (interface{}, error) { // noli ev = reflect.ValueOf(fmt.Sprint(ev)) } if !ev.Type().ConvertibleTo(et) { - return nil, conversionError("map value", ev, et) + return nil, conversionError("map element", ev, et) } result.SetMapIndex(key, ev.Convert(et)) } @@ -93,7 +121,6 @@ func Convert(value interface{}, typ reflect.Type) (interface{}, error) { // noli if ms, ok := value.(yaml.MapSlice); ok { result := reflect.MakeSlice(typ, 0, rv.Len()) for _, item := range ms { - // TODO something more nuanced if item.Value == nil { if et.Kind() >= reflect.Array { ev := reflect.Zero(et) @@ -106,7 +133,7 @@ func Convert(value interface{}, typ reflect.Type) (interface{}, error) { // noli ev = reflect.ValueOf(fmt.Sprint(ev)) } if !ev.Type().ConvertibleTo(et) { - return nil, conversionError("map value", ev, et) + return nil, conversionError("slice element", ev, et) } result = reflect.Append(result, ev.Convert(et)) } diff --git a/values/convert_test.go b/values/convert_test.go index f5b6d3b..a1ea044 100644 --- a/values/convert_test.go +++ b/values/convert_test.go @@ -7,6 +7,8 @@ import ( "testing" "time" + yaml "gopkg.in/yaml.v2" + "github.com/stretchr/testify/require" ) @@ -16,43 +18,52 @@ func (c redConvertible) ToLiquid() interface{} { return "red" } -func timeMustParse(s string) time.Time { - t, err := time.Parse(time.RFC3339, s) - if err != nil { - panic(err) - } - return t -} - -var convertTests = []struct { +var convertProtoTests = []struct { value, proto, expected interface{} }{ {1, 1.0, float64(1)}, {"2", 1, int(2)}, {"1.2", 1.0, float64(1.2)}, - {true, 1, 1}, - {false, 1, 0}, - {nil, true, false}, - {0, true, true}, - {"", true, true}, - {1, "", "1"}, - {false, "", "false"}, - {true, "", "true"}, - {"string", "", "string"}, - {[]int{1, 2}, []string{}, []string{"1", "2"}}, - // {"March 14, 2016", time.Now(), timeMustParse("2016-03-14T00:00:00Z")}, - {redConvertible{}, "", "red"}, } + +var convertTests = []struct { + value, expected interface{} +}{ + {nil, false}, + {true, 1}, + {false, 0}, + {0, true}, + {"", true}, + {1, "1"}, + {false, "false"}, + {true, "true"}, + {"string", "string"}, + {[]int{1, 2}, []interface{}{1, 2}}, + {[]interface{}{1, 2}, []int{1, 2}}, + {[]int{1, 2}, []string{"1", "2"}}, + {yaml.MapSlice{{Key: 1, Value: 1}}, []interface{}{1}}, + {yaml.MapSlice{{Key: 1, Value: 1}}, []string{"1"}}, + {yaml.MapSlice{{Key: 1, Value: "a"}}, []string{"a"}}, + {yaml.MapSlice{{Key: 1, Value: "a"}}, map[interface{}]interface{}{1: "a"}}, + {yaml.MapSlice{{Key: 1, Value: "a"}}, map[int]string{1: "a"}}, + {yaml.MapSlice{{Key: 1, Value: "a"}}, map[string]string{"1": "a"}}, + {yaml.MapSlice{{Key: "a", Value: 1}}, map[string]string{"a": "1"}}, + {yaml.MapSlice{{Key: "a", Value: nil}}, map[string]interface{}{"a": nil}}, + {yaml.MapSlice{{Key: nil, Value: 1}}, map[interface{}]string{nil: "1"}}, + // {"March 14, 2016", time.Now(), timeMustParse("2016-03-14T00:00:00Z")}, + {redConvertible{}, "red"}, +} + var convertErrorTests = []struct { value, proto, expected interface{} }{ {map[string]bool{"k": true}, map[int]bool{}, "map key"}, - {map[string]string{"k": "v"}, map[string]int{}, "map value"}, - {map[interface{}]interface{}{"k": "v"}, map[string]int{}, "map value"}, + {map[string]string{"k": "v"}, map[string]int{}, "map element"}, + {map[interface{}]interface{}{"k": "v"}, map[string]int{}, "map element"}, } func TestConvert(t *testing.T) { - for i, test := range convertTests { + for i, test := range convertProtoTests { t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) { typ := reflect.TypeOf(test.proto) name := fmt.Sprintf("Convert %#v -> %v", test.value, typ) @@ -61,6 +72,16 @@ func TestConvert(t *testing.T) { require.Equalf(t, test.expected, value, name) }) } + + for i, test := range convertTests { + t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) { + typ := reflect.TypeOf(test.expected) + name := fmt.Sprintf("Convert %#v -> %v", test.value, typ) + value, err := Convert(test.value, typ) + require.NoErrorf(t, err, name) + require.Equalf(t, test.expected, value, name) + }) + } } func TestConvert_errors(t *testing.T) { @@ -130,3 +151,11 @@ func TestMustConvertItem(t *testing.T) { require.Panics(t, func() { MustConvertItem("x", []int{}) }) } + +func timeMustParse(s string) time.Time { + t, err := time.Parse(time.RFC3339, s) + if err != nil { + panic(err) + } + return t +}