Skip to content

Commit 9c82a05

Browse files
committed
Add support for list types.
Added a list type, with tests. The tests are not exhaustive, but cover all the code paths for now.
1 parent ffefbe4 commit 9c82a05

File tree

2 files changed

+874
-0
lines changed

2 files changed

+874
-0
lines changed

types/list.go

+197
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package types
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/attr"
8+
"github.com/hashicorp/terraform-plugin-framework/internal/reflect"
9+
10+
"github.com/hashicorp/terraform-plugin-go/tftypes"
11+
)
12+
13+
var (
14+
_ attr.Type = ListType{}
15+
_ attr.Value = &List{}
16+
)
17+
18+
// ListType is an AttributeType representing a list of values. All values must
19+
// be of the same type, which the provider must specify as the ElemType
20+
// property.
21+
type ListType struct {
22+
ElemType attr.Type
23+
}
24+
25+
// TerraformType returns the tftypes.Type that should be used to
26+
// represent this type. This constrains what user input will be
27+
// accepted and what kind of data can be set in state. The framework
28+
// will use this to translate the AttributeType to something Terraform
29+
// can understand.
30+
func (l ListType) TerraformType(ctx context.Context) tftypes.Type {
31+
return tftypes.List{
32+
ElementType: l.ElemType.TerraformType(ctx),
33+
}
34+
}
35+
36+
// ValueFromTerraform returns an AttributeValue given a tftypes.Value.
37+
// This is meant to convert the tftypes.Value into a more convenient Go
38+
// type for the provider to consume the data with.
39+
func (l ListType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
40+
list := &List{
41+
ElemType: l.ElemType,
42+
}
43+
err := list.SetTerraformValue(ctx, in)
44+
return list, err
45+
}
46+
47+
// Equal returns true if `o` is also a ListType and has the same ElemType.
48+
func (l ListType) Equal(o attr.Type) bool {
49+
if l.ElemType == nil {
50+
return false
51+
}
52+
other, ok := o.(ListType)
53+
if !ok {
54+
return false
55+
}
56+
return l.ElemType.Equal(other.ElemType)
57+
}
58+
59+
// List represents a list of AttributeValues, all of the same type, indicated
60+
// by ElemType.
61+
type List struct {
62+
// Unknown will be set to true if the entire list is an unknown value.
63+
// If only some of the elements in the list are unknown, their known or
64+
// unknown status will be represented however that AttributeValue
65+
// surfaces that information. The List's Unknown property only tracks
66+
// if the number of elements in a List is known, not whether the
67+
// elements that are in the list are known.
68+
Unknown bool
69+
70+
// Null will be set to true if the list is null, either because it was
71+
// omitted from the configuration, state, or plan, or because it was
72+
// explicitly set to null.
73+
Null bool
74+
75+
// Elems are the elements in the list.
76+
Elems []attr.Value
77+
78+
// ElemType is the tftypes.Type of the elements in the list. All
79+
// elements in the list must be of this type.
80+
ElemType attr.Type
81+
}
82+
83+
// ElementsAs populates `target` with the elements of the List, throwing an
84+
// error if the elements cannot be stored in `target`.
85+
func (l *List) ElementsAs(ctx context.Context, target interface{}, allowUnhandled bool) error {
86+
// we need a tftypes.Value for this List to be able to use it with our
87+
// reflection code
88+
values := make([]tftypes.Value, 0, len(l.Elems))
89+
for pos, elem := range l.Elems {
90+
val, err := elem.ToTerraformValue(ctx)
91+
if err != nil {
92+
return fmt.Errorf("error getting Terraform value for element %d: %w", pos, err)
93+
}
94+
err = tftypes.ValidateValue(l.ElemType.TerraformType(ctx), val)
95+
if err != nil {
96+
return fmt.Errorf("error using created Terraform value for element %d: %w", pos, err)
97+
}
98+
values = append(values, tftypes.NewValue(l.ElemType.TerraformType(ctx), val))
99+
}
100+
return reflect.Into(ctx, tftypes.NewValue(tftypes.List{
101+
ElementType: l.ElemType.TerraformType(ctx),
102+
}, values), target, reflect.Options{
103+
UnhandledNullAsEmpty: allowUnhandled,
104+
UnhandledUnknownAsEmpty: allowUnhandled,
105+
})
106+
}
107+
108+
// ToTerraformValue returns the data contained in the AttributeValue as
109+
// a Go type that tftypes.NewValue will accept.
110+
func (l *List) ToTerraformValue(ctx context.Context) (interface{}, error) {
111+
if l.Unknown {
112+
return tftypes.UnknownValue, nil
113+
}
114+
if l.Null {
115+
return nil, nil
116+
}
117+
vals := make([]tftypes.Value, 0, len(l.Elems))
118+
for _, elem := range l.Elems {
119+
val, err := elem.ToTerraformValue(ctx)
120+
if err != nil {
121+
return nil, err
122+
}
123+
err = tftypes.ValidateValue(l.ElemType.TerraformType(ctx), val)
124+
if err != nil {
125+
return nil, err
126+
}
127+
vals = append(vals, tftypes.NewValue(l.ElemType.TerraformType(ctx), val))
128+
}
129+
return vals, nil
130+
}
131+
132+
// Equal must return true if the AttributeValue is considered
133+
// semantically equal to the AttributeValue passed as an argument.
134+
func (l *List) Equal(o attr.Value) bool {
135+
if l == nil {
136+
return false
137+
}
138+
other, ok := o.(*List)
139+
if !ok {
140+
return false
141+
}
142+
if l.Unknown != other.Unknown {
143+
return false
144+
}
145+
if l.Null != other.Null {
146+
return false
147+
}
148+
if !l.ElemType.Equal(other.ElemType) {
149+
return false
150+
}
151+
if len(l.Elems) != len(other.Elems) {
152+
return false
153+
}
154+
for pos, lElem := range l.Elems {
155+
oElem := other.Elems[pos]
156+
if !lElem.Equal(oElem) {
157+
return false
158+
}
159+
}
160+
return true
161+
}
162+
163+
// SetTerraformValue updates `l` to reflect the data stored in `in`.
164+
func (l *List) SetTerraformValue(ctx context.Context, in tftypes.Value) error {
165+
l.Unknown = false
166+
l.Null = false
167+
l.Elems = nil
168+
if !in.Type().Is(tftypes.List{}) {
169+
return fmt.Errorf("can't use %s as value of List, can only use tftypes.List values", in.String())
170+
}
171+
if !in.Type().Is(tftypes.List{ElementType: l.ElemType.TerraformType(ctx)}) {
172+
return fmt.Errorf("can't use %s as value of List with ElementType %T, can only use %s values", in.String(), l.ElemType, l.ElemType.TerraformType(ctx).String())
173+
}
174+
if !in.IsKnown() {
175+
l.Unknown = true
176+
return nil
177+
}
178+
if in.IsNull() {
179+
l.Null = true
180+
return nil
181+
}
182+
val := []tftypes.Value{}
183+
err := in.As(&val)
184+
if err != nil {
185+
return err
186+
}
187+
elems := make([]attr.Value, 0, len(val))
188+
for _, elem := range val {
189+
av, err := l.ElemType.ValueFromTerraform(ctx, elem)
190+
if err != nil {
191+
return err
192+
}
193+
elems = append(elems, av)
194+
}
195+
l.Elems = elems
196+
return nil
197+
}

0 commit comments

Comments
 (0)