Skip to content

Commit 3a790d5

Browse files
committed
types: Initial copy of List and ListType to Set and SetType
This initial copy does not include a real set implementation or handling for duplicate elements, however this sets the stage for expanding testing to cover the use case.
1 parent 7055f97 commit 3a790d5

13 files changed

+2234
-57
lines changed

.changelog/pending.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:feature
2+
types: Support `Set` and `SetType`
3+
```

internal/reflect/struct_test.go

+176-22
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,16 @@ func TestNewStruct_complex(t *testing.T) {
190190
t.Parallel()
191191

192192
type myStruct struct {
193-
Slice []string `tfsdk:"slice"`
194-
SliceOfStructs []struct {
193+
ListSlice []string `tfsdk:"list_slice"`
194+
ListSliceOfStructs []struct {
195195
A string `tfsdk:"a"`
196196
B int `tfsdk:"b"`
197-
} `tfsdk:"slice_of_structs"`
197+
} `tfsdk:"list_slice_of_structs"`
198+
SetSlice []string `tfsdk:"set_slice"`
199+
SetSliceOfStructs []struct {
200+
A string `tfsdk:"a"`
201+
B int `tfsdk:"b"`
202+
} `tfsdk:"set_slice_of_structs"`
198203
Struct struct {
199204
A bool `tfsdk:"a"`
200205
Slice []float64 `tfsdk:"slice"`
@@ -211,10 +216,21 @@ func TestNewStruct_complex(t *testing.T) {
211216
var s myStruct
212217
result, diags := refl.Struct(context.Background(), types.ObjectType{
213218
AttrTypes: map[string]attr.Type{
214-
"slice": types.ListType{
219+
"list_slice": types.ListType{
215220
ElemType: types.StringType,
216221
},
217-
"slice_of_structs": types.ListType{
222+
"list_slice_of_structs": types.ListType{
223+
ElemType: types.ObjectType{
224+
AttrTypes: map[string]attr.Type{
225+
"a": types.StringType,
226+
"b": types.NumberType,
227+
},
228+
},
229+
},
230+
"set_slice": types.SetType{
231+
ElemType: types.StringType,
232+
},
233+
"set_slice_of_structs": types.SetType{
218234
ElemType: types.ObjectType{
219235
AttrTypes: map[string]attr.Type{
220236
"a": types.StringType,
@@ -245,10 +261,21 @@ func TestNewStruct_complex(t *testing.T) {
245261
},
246262
}, tftypes.NewValue(tftypes.Object{
247263
AttributeTypes: map[string]tftypes.Type{
248-
"slice": tftypes.List{
264+
"list_slice": tftypes.List{
249265
ElementType: tftypes.String,
250266
},
251-
"slice_of_structs": tftypes.List{
267+
"list_slice_of_structs": tftypes.List{
268+
ElementType: tftypes.Object{
269+
AttributeTypes: map[string]tftypes.Type{
270+
"a": tftypes.String,
271+
"b": tftypes.Number,
272+
},
273+
},
274+
},
275+
"set_slice": tftypes.Set{
276+
ElementType: tftypes.String,
277+
},
278+
"set_slice_of_structs": tftypes.Set{
252279
ElementType: tftypes.Object{
253280
AttributeTypes: map[string]tftypes.Type{
254281
"a": tftypes.String,
@@ -278,14 +305,48 @@ func TestNewStruct_complex(t *testing.T) {
278305
"unhandled_unknown": tftypes.String,
279306
},
280307
}, map[string]tftypes.Value{
281-
"slice": tftypes.NewValue(tftypes.List{
308+
"list_slice": tftypes.NewValue(tftypes.List{
309+
ElementType: tftypes.String,
310+
}, []tftypes.Value{
311+
tftypes.NewValue(tftypes.String, "red"),
312+
tftypes.NewValue(tftypes.String, "blue"),
313+
tftypes.NewValue(tftypes.String, "green"),
314+
}),
315+
"list_slice_of_structs": tftypes.NewValue(tftypes.List{
316+
ElementType: tftypes.Object{
317+
AttributeTypes: map[string]tftypes.Type{
318+
"a": tftypes.String,
319+
"b": tftypes.Number,
320+
},
321+
},
322+
}, []tftypes.Value{
323+
tftypes.NewValue(tftypes.Object{
324+
AttributeTypes: map[string]tftypes.Type{
325+
"a": tftypes.String,
326+
"b": tftypes.Number,
327+
},
328+
}, map[string]tftypes.Value{
329+
"a": tftypes.NewValue(tftypes.String, "hello, world"),
330+
"b": tftypes.NewValue(tftypes.Number, 123),
331+
}),
332+
tftypes.NewValue(tftypes.Object{
333+
AttributeTypes: map[string]tftypes.Type{
334+
"a": tftypes.String,
335+
"b": tftypes.Number,
336+
},
337+
}, map[string]tftypes.Value{
338+
"a": tftypes.NewValue(tftypes.String, "goodnight, moon"),
339+
"b": tftypes.NewValue(tftypes.Number, 456),
340+
}),
341+
}),
342+
"set_slice": tftypes.NewValue(tftypes.Set{
282343
ElementType: tftypes.String,
283344
}, []tftypes.Value{
284345
tftypes.NewValue(tftypes.String, "red"),
285346
tftypes.NewValue(tftypes.String, "blue"),
286347
tftypes.NewValue(tftypes.String, "green"),
287348
}),
288-
"slice_of_structs": tftypes.NewValue(tftypes.List{
349+
"set_slice_of_structs": tftypes.NewValue(tftypes.Set{
289350
ElementType: tftypes.Object{
290351
AttributeTypes: map[string]tftypes.Type{
291352
"a": tftypes.String,
@@ -365,8 +426,22 @@ func TestNewStruct_complex(t *testing.T) {
365426
}
366427
str := "pointed"
367428
expected := myStruct{
368-
Slice: []string{"red", "blue", "green"},
369-
SliceOfStructs: []struct {
429+
ListSlice: []string{"red", "blue", "green"},
430+
ListSliceOfStructs: []struct {
431+
A string `tfsdk:"a"`
432+
B int `tfsdk:"b"`
433+
}{
434+
{
435+
A: "hello, world",
436+
B: 123,
437+
},
438+
{
439+
A: "goodnight, moon",
440+
B: 456,
441+
},
442+
},
443+
SetSlice: []string{"red", "blue", "green"},
444+
SetSliceOfStructs: []struct {
370445
A string `tfsdk:"a"`
371446
B int `tfsdk:"b"`
372447
}{
@@ -456,11 +531,16 @@ func TestFromStruct_complex(t *testing.T) {
456531
t.Parallel()
457532

458533
type myStruct struct {
459-
Slice []string `tfsdk:"slice"`
460-
SliceOfStructs []struct {
534+
ListSlice []string `tfsdk:"list_slice"`
535+
ListSliceOfStructs []struct {
536+
A string `tfsdk:"a"`
537+
B int `tfsdk:"b"`
538+
} `tfsdk:"list_slice_of_structs"`
539+
SetSlice []string `tfsdk:"set_slice"`
540+
SetSliceOfStructs []struct {
461541
A string `tfsdk:"a"`
462542
B int `tfsdk:"b"`
463-
} `tfsdk:"slice_of_structs"`
543+
} `tfsdk:"set_slice_of_structs"`
464544
Struct struct {
465545
A bool `tfsdk:"a"`
466546
Slice []float64 `tfsdk:"slice"`
@@ -477,8 +557,22 @@ func TestFromStruct_complex(t *testing.T) {
477557
}
478558
str := "pointed"
479559
s := myStruct{
480-
Slice: []string{"red", "blue", "green"},
481-
SliceOfStructs: []struct {
560+
ListSlice: []string{"red", "blue", "green"},
561+
ListSliceOfStructs: []struct {
562+
A string `tfsdk:"a"`
563+
B int `tfsdk:"b"`
564+
}{
565+
{
566+
A: "hello, world",
567+
B: 123,
568+
},
569+
{
570+
A: "goodnight, moon",
571+
B: 456,
572+
},
573+
},
574+
SetSlice: []string{"red", "blue", "green"},
575+
SetSliceOfStructs: []struct {
482576
A string `tfsdk:"a"`
483577
B int `tfsdk:"b"`
484578
}{
@@ -521,10 +615,21 @@ func TestFromStruct_complex(t *testing.T) {
521615
}
522616
result, diags := refl.FromStruct(context.Background(), types.ObjectType{
523617
AttrTypes: map[string]attr.Type{
524-
"slice": types.ListType{
618+
"list_slice": types.ListType{
525619
ElemType: types.StringType,
526620
},
527-
"slice_of_structs": types.ListType{
621+
"list_slice_of_structs": types.ListType{
622+
ElemType: types.ObjectType{
623+
AttrTypes: map[string]attr.Type{
624+
"a": types.StringType,
625+
"b": types.NumberType,
626+
},
627+
},
628+
},
629+
"set_slice": types.SetType{
630+
ElemType: types.StringType,
631+
},
632+
"set_slice_of_structs": types.SetType{
528633
ElemType: types.ObjectType{
529634
AttrTypes: map[string]attr.Type{
530635
"a": types.StringType,
@@ -560,10 +665,21 @@ func TestFromStruct_complex(t *testing.T) {
560665
}
561666
expected := types.Object{
562667
AttrTypes: map[string]attr.Type{
563-
"slice": types.ListType{
668+
"list_slice": types.ListType{
669+
ElemType: types.StringType,
670+
},
671+
"list_slice_of_structs": types.ListType{
672+
ElemType: types.ObjectType{
673+
AttrTypes: map[string]attr.Type{
674+
"a": types.StringType,
675+
"b": types.NumberType,
676+
},
677+
},
678+
},
679+
"set_slice": types.SetType{
564680
ElemType: types.StringType,
565681
},
566-
"slice_of_structs": types.ListType{
682+
"set_slice_of_structs": types.SetType{
567683
ElemType: types.ObjectType{
568684
AttrTypes: map[string]attr.Type{
569685
"a": types.StringType,
@@ -594,15 +710,53 @@ func TestFromStruct_complex(t *testing.T) {
594710
"uint": types.NumberType,
595711
},
596712
Attrs: map[string]attr.Value{
597-
"slice": types.List{
713+
"list_slice": types.List{
714+
ElemType: types.StringType,
715+
Elems: []attr.Value{
716+
types.String{Value: "red"},
717+
types.String{Value: "blue"},
718+
types.String{Value: "green"},
719+
},
720+
},
721+
"list_slice_of_structs": types.List{
722+
ElemType: types.ObjectType{
723+
AttrTypes: map[string]attr.Type{
724+
"a": types.StringType,
725+
"b": types.NumberType,
726+
},
727+
},
728+
Elems: []attr.Value{
729+
types.Object{
730+
AttrTypes: map[string]attr.Type{
731+
"a": types.StringType,
732+
"b": types.NumberType,
733+
},
734+
Attrs: map[string]attr.Value{
735+
"a": types.String{Value: "hello, world"},
736+
"b": types.Number{Value: big.NewFloat(123)},
737+
},
738+
},
739+
types.Object{
740+
AttrTypes: map[string]attr.Type{
741+
"a": types.StringType,
742+
"b": types.NumberType,
743+
},
744+
Attrs: map[string]attr.Value{
745+
"a": types.String{Value: "goodnight, moon"},
746+
"b": types.Number{Value: big.NewFloat(456)},
747+
},
748+
},
749+
},
750+
},
751+
"set_slice": types.Set{
598752
ElemType: types.StringType,
599753
Elems: []attr.Value{
600754
types.String{Value: "red"},
601755
types.String{Value: "blue"},
602756
types.String{Value: "green"},
603757
},
604758
},
605-
"slice_of_structs": types.List{
759+
"set_slice_of_structs": types.Set{
606760
ElemType: types.ObjectType{
607761
AttrTypes: map[string]attr.Type{
608762
"a": types.StringType,

tfsdk/attribute.go

+43-2
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,49 @@ func (a Attribute) validate(ctx context.Context, req ValidateAttributeRequest, r
303303
}
304304
}
305305
case NestingModeSet:
306-
// TODO: Set implementation
307-
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/53
306+
s, ok := req.AttributeConfig.(types.Set)
307+
308+
if !ok {
309+
err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath)
310+
resp.Diagnostics.AddAttributeError(
311+
req.AttributePath,
312+
"Attribute Validation Error",
313+
"Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(),
314+
)
315+
316+
return
317+
}
318+
319+
for _, value := range s.Elems {
320+
tfValueRaw, err := value.ToTerraformValue(ctx)
321+
322+
if err != nil {
323+
err := fmt.Errorf("error running ToTerraformValue on element value: %v", value)
324+
resp.Diagnostics.AddAttributeError(
325+
req.AttributePath,
326+
"Attribute Validation Error",
327+
"Attribute validation cannot convert element into a Terraform value. Report this to the provider developer:\n\n"+err.Error(),
328+
)
329+
330+
return
331+
}
332+
333+
tfValue := tftypes.NewValue(s.ElemType.TerraformType(ctx), tfValueRaw)
334+
335+
for nestedName, nestedAttr := range a.Attributes.GetAttributes() {
336+
nestedAttrReq := ValidateAttributeRequest{
337+
AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(nestedName),
338+
Config: req.Config,
339+
}
340+
nestedAttrResp := &ValidateAttributeResponse{
341+
Diagnostics: resp.Diagnostics,
342+
}
343+
344+
nestedAttr.validate(ctx, nestedAttrReq, nestedAttrResp)
345+
346+
resp.Diagnostics = nestedAttrResp.Diagnostics
347+
}
348+
}
308349
case NestingModeMap:
309350
m, ok := req.AttributeConfig.(types.Map)
310351

0 commit comments

Comments
 (0)