Merge "Fix resolving of slices / arrays / maps where keys need converting." into studio-1.4-dev automerge: 73dbd04
automerge: 5a3e60c

* commit '5a3e60cecae46ea74dc7ecf365927480dbf9a831':
  Fix resolving of slices / arrays / maps where keys need converting.
diff --git a/builder/get_set_test.go b/builder/get_set_test.go
index 42c23ad..c27a946 100644
--- a/builder/get_set_test.go
+++ b/builder/get_set_test.go
@@ -103,6 +103,7 @@
 		{p.Atoms().Index(1).Field("Str").ArrayIndex(1), byte('y'), nil},
 		{p.Atoms().Index(1).Field("Str").Slice(1, 3), "yz", nil},
 		{p.Atoms().Index(1).Field("Map").MapIndex("bird"), "tweet", nil},
+		{p.Atoms().Index(1).Field("Map").MapIndex([]rune("bird")), "tweet", nil},
 
 		// Test invalid paths
 		{p.Atoms().Index(5), nil, fmt.Errorf(
@@ -125,8 +126,8 @@
 			"Index at Capture(%v).Atoms[1].Str[4] is out of bounds [0-2]", p.ID)},
 		{p.Atoms().Index(1).Field("Ptr").ArrayIndex(4), nil, fmt.Errorf(
 			"Type *builder.testStruct at Capture(%v).Atoms[1].Ptr is not an array, slice or string", p.ID)},
-		{p.Atoms().Index(1).Field("Map").MapIndex(10), nil, fmt.Errorf(
-			"Map at Capture(%v).Atoms[1].Map has key of type string, got type int", p.ID)},
+		{p.Atoms().Index(1).Field("Map").MapIndex(10.0), nil, fmt.Errorf(
+			"Map at Capture(%v).Atoms[1].Map has key of type string, got type float64", p.ID)},
 		{p.Atoms().Index(1).Field("Map").MapIndex("rabbit"), nil, fmt.Errorf(
 			"Map at Capture(%v).Atoms[1].Map does not contain key rabbit", p.ID)},
 		{p.Atoms().Index(1).Field("Ptr").MapIndex("foo"), nil, fmt.Errorf(
@@ -155,6 +156,9 @@
 		{path: p.Atoms().Index(0).Field("Any"), val: 0.123},
 		{path: p.Atoms().Index(0).Field("Ptr"), val: &testStruct{Str: "ddd"}},
 		{path: p.Atoms().Index(0).Field("Ptr").Field("Str"), val: "purr"},
+		{path: p.Atoms().Index(1).Field("Sli").ArrayIndex(1), val: false},
+		{path: p.Atoms().Index(1).Field("Map").MapIndex("bird"), val: "churp"},
+		{path: p.Atoms().Index(1).Field("Map").MapIndex([]rune("bird")), val: "churp"},
 
 		// Test invalid paths
 		{p.Atoms().Index(5), nil, fmt.Errorf(
@@ -175,12 +179,18 @@
 			"Index at Capture(%v).Atoms[1].Str[4] is out of bounds [0-2]", p.ID)},
 		{p.Atoms().Index(1).Field("Ptr").ArrayIndex(4), nil, fmt.Errorf(
 			"Type *builder.testStruct at Capture(%v).Atoms[1].Ptr is not an array, slice or string", p.ID)},
-		{p.Atoms().Index(1).Field("Map").MapIndex(10), nil, fmt.Errorf(
-			"Map at Capture(%v).Atoms[1].Map has key of type string, got type int", p.ID)},
+		{p.Atoms().Index(1).Field("Map").MapIndex(10.0), nil, fmt.Errorf(
+			"Map at Capture(%v).Atoms[1].Map has key of type string, got type float64", p.ID)},
 		{p.Atoms().Index(1).Field("Map").MapIndex("rabbit"), nil, fmt.Errorf(
 			"Map at Capture(%v).Atoms[1].Map does not contain key rabbit", p.ID)},
 		{p.Atoms().Index(1).Field("Ptr").MapIndex("foo"), nil, fmt.Errorf(
 			"Type *builder.testStruct at Capture(%v).Atoms[1].Ptr is not a map", p.ID)},
+
+		// Test invalid sets
+		{p.Atoms().Index(1).Field("Sli").ArrayIndex(2), "blah", fmt.Errorf(
+			"Slice or array at Capture(%v).Atoms[1].Sli has element of type bool, got type string", p.ID)},
+		{p.Atoms().Index(1).Field("Map").MapIndex("bird"), 10.0, fmt.Errorf(
+			"Map at Capture(%v).Atoms[1].Map has value of type string, got type float64", p.ID)},
 	} {
 		res, err := database.Build(&Set{Path: test.path, Value: test.val}, d, l)
 		if expected := test.err; !reflect.DeepEqual(err, expected) {
diff --git a/builder/resolve.go b/builder/resolve.go
index 16613a3..aaea591 100644
--- a/builder/resolve.go
+++ b/builder/resolve.go
@@ -215,12 +215,12 @@
 			m := reflect.ValueOf(v[i-1])
 			switch m.Kind() {
 			case reflect.Map:
-				key := reflect.ValueOf(p.Key)
-				if key.Type() != m.Type().Key() {
+				key, ok := convert(reflect.ValueOf(p.Key), m.Type().Key())
+				if !ok {
 					return nil, fmt.Errorf("Map at %s has key of type %v, got type %v",
 						paths[i-1].Path(), m.Type().Key(), key.Type())
 				}
-				val := m.MapIndex(reflect.ValueOf(p.Key))
+				val := m.MapIndex(key)
 				if !val.IsValid() {
 					return nil, fmt.Errorf("Map at %s does not contain key %v",
 						paths[i-1].Path(), p.Key)
@@ -239,3 +239,14 @@
 
 	return v, nil
 }
+
+func convert(val reflect.Value, ty reflect.Type) (reflect.Value, bool) {
+	if valTy := val.Type(); valTy != ty {
+		if valTy.ConvertibleTo(ty) {
+			val = val.Convert(ty)
+		} else {
+			return val, false
+		}
+	}
+	return val, true
+}
diff --git a/builder/set.go b/builder/set.go
index 3bbf6c7..092b5f1 100644
--- a/builder/set.go
+++ b/builder/set.go
@@ -29,8 +29,8 @@
 // with the object, value or memory at p replaced with v. The path returned is
 // identical to p, but with the base changed to refer to the new capture.
 func (request *Set) BuildLazy(c interface{}, d database.Database, l log.Logger) (interface{}, error) {
-	p := path.Flatten(request.Path)
-	v, err := resolveChain(p, d, l)
+	paths := path.Flatten(request.Path)
+	v, err := resolveChain(paths, d, l)
 	if err != nil {
 		return nil, err
 	}
@@ -40,7 +40,7 @@
 
 	// Propagate changes back down to the root
 	for i := len(v) - 1; i >= 0; i-- {
-		switch p := p[i].(type) {
+		switch p := paths[i].(type) {
 		case *path.Capture:
 			id, err := database.Store(v[i], d, l)
 			if err != nil {
@@ -101,6 +101,11 @@
 			if err != nil {
 				return nil, err
 			}
+			val, ok := convert(reflect.ValueOf(v[i]), a.Type().Elem())
+			if !ok {
+				return nil, fmt.Errorf("Slice or array at %s has element of type %v, got type %v",
+					paths[i-1].Path(), a.Type().Elem(), val.Type())
+			}
 			if err := assign(a.Index(int(p.Index)), reflect.ValueOf(v[i])); err != nil {
 				return nil, err
 			}
@@ -111,9 +116,17 @@
 			if err != nil {
 				return nil, err
 			}
-			if err := assign(m.MapIndex(reflect.ValueOf(p.Key)), reflect.ValueOf(v[i])); err != nil {
-				return nil, err
+			key, ok := convert(reflect.ValueOf(p.Key), m.Type().Key())
+			if !ok {
+				return nil, fmt.Errorf("Map at %s has key of type %v, got type %v",
+					paths[i-1].Path(), m.Type().Key(), key.Type())
 			}
+			val, ok := convert(reflect.ValueOf(v[i]), m.Type().Elem())
+			if !ok {
+				return nil, fmt.Errorf("Map at %s has value of type %v, got type %v",
+					paths[i-1].Path(), m.Type().Elem(), val.Type())
+			}
+			m.SetMapIndex(key, val)
 			v[i-1] = m.Interface()
 
 		default:
@@ -125,7 +138,15 @@
 }
 
 func clone(v reflect.Value) (reflect.Value, error) {
-	o := reflect.New(v.Type()).Elem()
+	var o reflect.Value
+	switch v.Kind() {
+	case reflect.Slice:
+		o = reflect.MakeSlice(v.Type(), v.Len(), v.Len())
+	case reflect.Map:
+		o = reflect.MakeMap(v.Type())
+	default:
+		o = reflect.New(v.Type()).Elem()
+	}
 	return o, shallowCopy(o, v)
 }
 
@@ -143,7 +164,8 @@
 
 	case reflect.Map:
 		for _, k := range src.MapKeys() {
-			dst.SetMapIndex(k, src.MapIndex(k))
+			val := src.MapIndex(k)
+			dst.SetMapIndex(k, val)
 		}
 
 	default: