Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

types: Support Set and SetType #126

Merged
merged 9 commits into from
Sep 14, 2021
7 changes: 7 additions & 0 deletions .changelog/126.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:feature
types: Support `Set` and `SetType`
```

```release-note:bug
attr: Ensure `List` types implementing `attr.TypeWithValidate` call `ElementType` validation only if that type implements `attr.TypeWithValidate`
```
29 changes: 24 additions & 5 deletions internal/reflect/slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,14 @@ func reflectSlice(ctx context.Context, typ attr.Type, val tftypes.Value, target
targetValue := reflect.Zero(elemType)

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

if typ.TerraformType(ctx).Is(tftypes.Set{}) {
valPath = path.WithElementKeyValue(value)
}

// reflect the value into our new target
val, valDiags := BuildValue(ctx, elemAttrType, value, targetValue, opts, path)
val, valDiags := BuildValue(ctx, elemAttrType, value, targetValue, opts, valPath)
diags.Append(valDiags...)

if diags.HasError() {
Expand Down Expand Up @@ -135,25 +139,40 @@ func FromSlice(ctx context.Context, typ attr.Type, val reflect.Value, path *tfty
elemType := t.ElementType()
tfElems := make([]tftypes.Value, 0, val.Len())
for i := 0; i < val.Len(); i++ {
val, valDiags := FromValue(ctx, elemType, val.Index(i).Interface(), path.WithElementKeyInt(int64(i)))
// The underlying reflect.Slice is fetched by Index(). For set types,
// the path is value-based instead of index-based. Since there is only
// the index until the value is retrieved, this will pass the
// technically incorrect index-based path at first for framework
// debugging purposes, then correct the path afterwards.
valPath := path.WithElementKeyInt(int64(i))

val, valDiags := FromValue(ctx, elemType, val.Index(i).Interface(), valPath)
diags.Append(valDiags...)

if diags.HasError() {
return nil, diags
}

tfVal, err := val.ToTerraformValue(ctx)

if err != nil {
return nil, append(diags, toTerraformValueErrorDiag(err, path))
}

err = tftypes.ValidateValue(elemType.TerraformType(ctx), tfVal)

if err != nil {
return nil, append(diags, validateValueErrorDiag(err, path))
}

tfElemVal := tftypes.NewValue(elemType.TerraformType(ctx), tfVal)

if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok {
diags.Append(typeWithValidate.Validate(ctx, tfElemVal, path.WithElementKeyInt(int64(i)))...)
if tfType.Is(tftypes.Set{}) {
valPath = path.WithElementKeyValue(tfElemVal)
}

if typeWithValidate, ok := elemType.(attr.TypeWithValidate); ok {
diags.Append(typeWithValidate.Validate(ctx, tfElemVal, valPath)...)

if diags.HasError() {
return nil, diags
Expand Down
198 changes: 176 additions & 22 deletions internal/reflect/struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,16 @@ func TestNewStruct_complex(t *testing.T) {
t.Parallel()

type myStruct struct {
Slice []string `tfsdk:"slice"`
SliceOfStructs []struct {
ListSlice []string `tfsdk:"list_slice"`
ListSliceOfStructs []struct {
A string `tfsdk:"a"`
B int `tfsdk:"b"`
} `tfsdk:"slice_of_structs"`
} `tfsdk:"list_slice_of_structs"`
SetSlice []string `tfsdk:"set_slice"`
SetSliceOfStructs []struct {
A string `tfsdk:"a"`
B int `tfsdk:"b"`
} `tfsdk:"set_slice_of_structs"`
Struct struct {
A bool `tfsdk:"a"`
Slice []float64 `tfsdk:"slice"`
Expand All @@ -226,10 +231,21 @@ func TestNewStruct_complex(t *testing.T) {
var s myStruct
result, diags := refl.Struct(context.Background(), types.ObjectType{
AttrTypes: map[string]attr.Type{
"slice": types.ListType{
"list_slice": types.ListType{
ElemType: types.StringType,
},
"slice_of_structs": types.ListType{
"list_slice_of_structs": types.ListType{
ElemType: types.ObjectType{
AttrTypes: map[string]attr.Type{
"a": types.StringType,
"b": types.NumberType,
},
},
},
"set_slice": types.SetType{
ElemType: types.StringType,
},
"set_slice_of_structs": types.SetType{
ElemType: types.ObjectType{
AttrTypes: map[string]attr.Type{
"a": types.StringType,
Expand Down Expand Up @@ -260,10 +276,21 @@ func TestNewStruct_complex(t *testing.T) {
},
}, tftypes.NewValue(tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"slice": tftypes.List{
"list_slice": tftypes.List{
ElementType: tftypes.String,
},
"slice_of_structs": tftypes.List{
"list_slice_of_structs": tftypes.List{
ElementType: tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"a": tftypes.String,
"b": tftypes.Number,
},
},
},
"set_slice": tftypes.Set{
ElementType: tftypes.String,
},
"set_slice_of_structs": tftypes.Set{
ElementType: tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"a": tftypes.String,
Expand Down Expand Up @@ -293,14 +320,48 @@ func TestNewStruct_complex(t *testing.T) {
"unhandled_unknown": tftypes.String,
},
}, map[string]tftypes.Value{
"slice": tftypes.NewValue(tftypes.List{
"list_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"),
}),
"list_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),
}),
}),
"set_slice": tftypes.NewValue(tftypes.Set{
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{
"set_slice_of_structs": tftypes.NewValue(tftypes.Set{
ElementType: tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"a": tftypes.String,
Expand Down Expand Up @@ -380,8 +441,22 @@ func TestNewStruct_complex(t *testing.T) {
}
str := "pointed"
expected := myStruct{
Slice: []string{"red", "blue", "green"},
SliceOfStructs: []struct {
ListSlice: []string{"red", "blue", "green"},
ListSliceOfStructs: []struct {
A string `tfsdk:"a"`
B int `tfsdk:"b"`
}{
{
A: "hello, world",
B: 123,
},
{
A: "goodnight, moon",
B: 456,
},
},
SetSlice: []string{"red", "blue", "green"},
SetSliceOfStructs: []struct {
A string `tfsdk:"a"`
B int `tfsdk:"b"`
}{
Expand Down Expand Up @@ -471,11 +546,16 @@ func TestFromStruct_complex(t *testing.T) {
t.Parallel()

type myStruct struct {
Slice []string `tfsdk:"slice"`
SliceOfStructs []struct {
ListSlice []string `tfsdk:"list_slice"`
ListSliceOfStructs []struct {
A string `tfsdk:"a"`
B int `tfsdk:"b"`
} `tfsdk:"list_slice_of_structs"`
SetSlice []string `tfsdk:"set_slice"`
SetSliceOfStructs []struct {
A string `tfsdk:"a"`
B int `tfsdk:"b"`
} `tfsdk:"slice_of_structs"`
} `tfsdk:"set_slice_of_structs"`
Struct struct {
A bool `tfsdk:"a"`
Slice []float64 `tfsdk:"slice"`
Expand All @@ -492,8 +572,22 @@ func TestFromStruct_complex(t *testing.T) {
}
str := "pointed"
s := myStruct{
Slice: []string{"red", "blue", "green"},
SliceOfStructs: []struct {
ListSlice: []string{"red", "blue", "green"},
ListSliceOfStructs: []struct {
A string `tfsdk:"a"`
B int `tfsdk:"b"`
}{
{
A: "hello, world",
B: 123,
},
{
A: "goodnight, moon",
B: 456,
},
},
SetSlice: []string{"red", "blue", "green"},
SetSliceOfStructs: []struct {
A string `tfsdk:"a"`
B int `tfsdk:"b"`
}{
Expand Down Expand Up @@ -536,10 +630,21 @@ func TestFromStruct_complex(t *testing.T) {
}
result, diags := refl.FromStruct(context.Background(), types.ObjectType{
AttrTypes: map[string]attr.Type{
"slice": types.ListType{
"list_slice": types.ListType{
ElemType: types.StringType,
},
"slice_of_structs": types.ListType{
"list_slice_of_structs": types.ListType{
ElemType: types.ObjectType{
AttrTypes: map[string]attr.Type{
"a": types.StringType,
"b": types.NumberType,
},
},
},
"set_slice": types.SetType{
ElemType: types.StringType,
},
"set_slice_of_structs": types.SetType{
ElemType: types.ObjectType{
AttrTypes: map[string]attr.Type{
"a": types.StringType,
Expand Down Expand Up @@ -575,10 +680,21 @@ func TestFromStruct_complex(t *testing.T) {
}
expected := types.Object{
AttrTypes: map[string]attr.Type{
"slice": types.ListType{
"list_slice": types.ListType{
ElemType: types.StringType,
},
"list_slice_of_structs": types.ListType{
ElemType: types.ObjectType{
AttrTypes: map[string]attr.Type{
"a": types.StringType,
"b": types.NumberType,
},
},
},
"set_slice": types.SetType{
ElemType: types.StringType,
},
"slice_of_structs": types.ListType{
"set_slice_of_structs": types.SetType{
ElemType: types.ObjectType{
AttrTypes: map[string]attr.Type{
"a": types.StringType,
Expand Down Expand Up @@ -609,15 +725,53 @@ func TestFromStruct_complex(t *testing.T) {
"uint": types.NumberType,
},
Attrs: map[string]attr.Value{
"slice": types.List{
"list_slice": types.List{
ElemType: types.StringType,
Elems: []attr.Value{
types.String{Value: "red"},
types.String{Value: "blue"},
types.String{Value: "green"},
},
},
"list_slice_of_structs": types.List{
ElemType: types.ObjectType{
AttrTypes: map[string]attr.Type{
"a": types.StringType,
"b": types.NumberType,
},
},
Elems: []attr.Value{
types.Object{
AttrTypes: map[string]attr.Type{
"a": types.StringType,
"b": types.NumberType,
},
Attrs: map[string]attr.Value{
"a": types.String{Value: "hello, world"},
"b": types.Number{Value: big.NewFloat(123)},
},
},
types.Object{
AttrTypes: map[string]attr.Type{
"a": types.StringType,
"b": types.NumberType,
},
Attrs: map[string]attr.Value{
"a": types.String{Value: "goodnight, moon"},
"b": types.Number{Value: big.NewFloat(456)},
},
},
},
},
"set_slice": types.Set{
ElemType: types.StringType,
Elems: []attr.Value{
types.String{Value: "red"},
types.String{Value: "blue"},
types.String{Value: "green"},
},
},
"slice_of_structs": types.List{
"set_slice_of_structs": types.Set{
ElemType: types.ObjectType{
AttrTypes: map[string]attr.Type{
"a": types.StringType,
Expand Down
Loading