Skip to content

Commit

Permalink
Text objects, fix slices, simplify primitives.
Browse files Browse the repository at this point in the history
Add some more tests for object to struct conversion.

Remove a log line. Oops.

Simplify primitives by passing a concrete type to tftypes.Value.As and
then setting a reflect.Value from the result. This avoids shenanigans
around pointers on reflect.Value types.

Make elements of a slice always settable by adding the reflect.Value to
the slice, then retrieving it by index, before recursing into it with
the `into` function. This means even when we add a new reflect.Value to
a slice, we can still set its value.

Add TODOs for the object tests we should probably write and the fact
that we've just ignored maps entirely so far.
  • Loading branch information
paddycarver committed May 27, 2021
1 parent 4438137 commit 2050100
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 13 deletions.
2 changes: 0 additions & 2 deletions internal/reflect/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package reflect
import (
"context"
"fmt"
"log"
"reflect"
"strings"

Expand Down Expand Up @@ -61,7 +60,6 @@ func reflectObjectIntoStruct(ctx context.Context, object tftypes.Value, target r
structValue := trueReflectValue(target)
for field, structFieldPos := range targetFields {
structField := structValue.Field(structFieldPos)
log.Println("reflecting", objectFields[field], "into", structField.Type())
err := into(ctx, objectFields[field], structField, opts, path.WithAttributeName(field))
if err != nil {
return err
Expand Down
104 changes: 104 additions & 0 deletions internal/reflect/object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,112 @@ func TestReflectObjectIntoStruct_primitives(t *testing.T) {
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
if s.A != "hello" {
t.Errorf("Expected s.A to be %q, was %q", "hello", s.A)
}
if s.B.Cmp(big.NewFloat(123)) != 0 {
t.Errorf("Expected s.B to be %v, was %v", big.NewFloat(123), s.B)
}
if s.C != true {
t.Errorf("Expected s.C to be %v, was %v", true, s.C)
}
}

func TestReflectObjectIntoStruct_complex(t *testing.T) {
t.Parallel()

var s struct {
Slice []string `tfsdk:"slice"`
SliceOfStructs []struct {
A string `tfsdk:"a"`
B int `tfsdk:"b"`
} `tfsdk:"slice_of_structs"`
Struct struct {
A bool `tfsdk:"a"`
Slice []float64 `tfsdk:"slice"`
} `tfsdk:"struct"`
// TODO: add map
// TODO: add tfsdk.AttributeValue
// TODO: add setUnknownAble
// TODO: add setNullable
// TODO: add tftypes.ValueConverter
}
err := reflectObjectIntoStruct(context.Background(), tftypes.NewValue(tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"slice": tftypes.List{
ElementType: tftypes.String,
},
"slice_of_structs": tftypes.List{
ElementType: tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"a": tftypes.String,
"b": tftypes.Number,
},
},
},
"struct": tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"a": tftypes.Bool,
"slice": tftypes.List{
ElementType: tftypes.Number,
},
},
},
},
}, map[string]tftypes.Value{
"slice": tftypes.NewValue(tftypes.List{
ElementType: tftypes.String,
}, []tftypes.Value{
tftypes.NewValue(tftypes.String, "red"),
tftypes.NewValue(tftypes.String, "blue"),
tftypes.NewValue(tftypes.String, "green"),
}),
"slice_of_structs": tftypes.NewValue(tftypes.List{
ElementType: tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"a": tftypes.String,
"b": tftypes.Number,
},
},
}, []tftypes.Value{
tftypes.NewValue(tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"a": tftypes.String,
"b": tftypes.Number,
},
}, map[string]tftypes.Value{
"a": tftypes.NewValue(tftypes.String, "hello, world"),
"b": tftypes.NewValue(tftypes.Number, 123),
}),
tftypes.NewValue(tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"a": tftypes.String,
"b": tftypes.Number,
},
}, map[string]tftypes.Value{
"a": tftypes.NewValue(tftypes.String, "goodnight, moon"),
"b": tftypes.NewValue(tftypes.Number, 456),
}),
}),
"struct": tftypes.NewValue(tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"a": tftypes.Bool,
"slice": tftypes.List{
ElementType: tftypes.Number,
},
},
}, map[string]tftypes.Value{
"a": tftypes.NewValue(tftypes.Bool, true),
"slice": tftypes.NewValue(tftypes.List{
ElementType: tftypes.Number,
}, []tftypes.Value{
tftypes.NewValue(tftypes.Number, 123),
tftypes.NewValue(tftypes.Number, 456),
tftypes.NewValue(tftypes.Number, 789),
}),
}),
}), reflect.ValueOf(&s), Options{}, tftypes.NewAttributePath())
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
}
22 changes: 17 additions & 5 deletions internal/reflect/primitive.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,24 @@ import (

func reflectPrimitive(ctx context.Context, val tftypes.Value, target reflect.Value, path *tftypes.AttributePath) error {
realValue := trueReflectValue(target)
if !realValue.CanAddr() {
return path.NewErrorf("can't obtain address of %s", target.Type())
if !realValue.CanSet() {
return path.NewErrorf("can't set %s", target.Type())
}
err := val.As(realValue.Addr().Interface())
if err != nil {
return path.NewError(err)
switch realValue.Kind() {
case reflect.Bool:
var b bool
err := val.As(&b)
if err != nil {
return path.NewError(err)
}
realValue.SetBool(b)
case reflect.String:
var s string
err := val.As(&s)
if err != nil {
return path.NewError(err)
}
realValue.SetString(s)
}
return nil
}
2 changes: 1 addition & 1 deletion internal/reflect/primitive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestReflectPrimitive_string(t *testing.T) {
if err == nil {
t.Error("Expected error, didn't get one")
}
if expected := ": can't obtain address of string"; expected != err.Error() {
if expected := ": can't set string"; expected != err.Error() {
t.Errorf("Expected error to be %q, got %q", expected, err.Error())
}
}
Expand Down
3 changes: 3 additions & 0 deletions internal/reflect/reflection.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ func into(ctx context.Context, val tftypes.Value, target reflect.Value, opts Opt
return reflectNumber(ctx, val, target, opts, path)
case reflect.Slice:
return reflectSlice(ctx, val, target, opts, path)
case reflect.Map:
// TODO: handle reflect.Map
return path.NewErrorf("reflecting into maps is not implemented yet.")
default:
return path.NewErrorf("don't know how to reflect %s into %s", val.Type(), target.Type())
}
Expand Down
10 changes: 5 additions & 5 deletions internal/reflect/slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,19 @@ func reflectSlice(ctx context.Context, val tftypes.Value, target reflect.Value,
// type for them, and add it to our new slice
for pos, value := range values {
// create a new Go value of the type that can go in the slice
targetValue := reflect.New(elemType)
targetValue := reflect.Zero(elemType)

// add the new target to our slice
sliced = reflect.Append(sliced, targetValue)

// update our path so we can have nice errors
path := path.WithElementKeyInt(int64(pos))

// reflect the value into our new target
err := into(ctx, value, targetValue, opts, path)
err := into(ctx, value, sliced.Index(sliced.Len()-1), opts, path)
if err != nil {
return err
}

// add the new target to our slice
sliced = reflect.Append(sliced, targetValue)
}

// update the target to be our slice
Expand Down

0 comments on commit 2050100

Please sign in to comment.