Skip to content

Add support for list types. #36

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

Merged
merged 3 commits into from
Jun 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/36.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
Added support for lists.
```
197 changes: 197 additions & 0 deletions types/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package types

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/reflect"

"github.com/hashicorp/terraform-plugin-go/tftypes"
)

var (
_ attr.Type = ListType{}
_ attr.Value = &List{}
)

// ListType is an AttributeType representing a list of values. All values must
// be of the same type, which the provider must specify as the ElemType
// property.
type ListType struct {
ElemType attr.Type
}

// TerraformType returns the tftypes.Type that should be used to
// represent this type. This constrains what user input will be
// accepted and what kind of data can be set in state. The framework
// will use this to translate the AttributeType to something Terraform
// can understand.
func (l ListType) TerraformType(ctx context.Context) tftypes.Type {
return tftypes.List{
ElementType: l.ElemType.TerraformType(ctx),
}
}

// ValueFromTerraform returns an AttributeValue given a tftypes.Value.
// This is meant to convert the tftypes.Value into a more convenient Go
// type for the provider to consume the data with.
func (l ListType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
list := &List{
ElemType: l.ElemType,
}
err := list.SetTerraformValue(ctx, in)
return list, err
}

// Equal returns true if `o` is also a ListType and has the same ElemType.
func (l ListType) Equal(o attr.Type) bool {
if l.ElemType == nil {
return false
}
other, ok := o.(ListType)
if !ok {
return false
}
return l.ElemType.Equal(other.ElemType)
}

// List represents a list of AttributeValues, all of the same type, indicated
// by ElemType.
type List struct {
// Unknown will be set to true if the entire list is an unknown value.
// If only some of the elements in the list are unknown, their known or
// unknown status will be represented however that AttributeValue
// surfaces that information. The List's Unknown property only tracks
// if the number of elements in a List is known, not whether the
// elements that are in the list are known.
Unknown bool

// Null will be set to true if the list is null, either because it was
// omitted from the configuration, state, or plan, or because it was
// explicitly set to null.
Null bool

// Elems are the elements in the list.
Elems []attr.Value

// ElemType is the tftypes.Type of the elements in the list. All
// elements in the list must be of this type.
ElemType attr.Type
}

// ElementsAs populates `target` with the elements of the List, throwing an
// error if the elements cannot be stored in `target`.
func (l *List) ElementsAs(ctx context.Context, target interface{}, allowUnhandled bool) error {
// we need a tftypes.Value for this List to be able to use it with our
// reflection code
values := make([]tftypes.Value, 0, len(l.Elems))
for pos, elem := range l.Elems {
val, err := elem.ToTerraformValue(ctx)
if err != nil {
return fmt.Errorf("error getting Terraform value for element %d: %w", pos, err)
}
err = tftypes.ValidateValue(l.ElemType.TerraformType(ctx), val)
if err != nil {
return fmt.Errorf("error using created Terraform value for element %d: %w", pos, err)
}
values = append(values, tftypes.NewValue(l.ElemType.TerraformType(ctx), val))
}
return reflect.Into(ctx, tftypes.NewValue(tftypes.List{
ElementType: l.ElemType.TerraformType(ctx),
}, values), target, reflect.Options{
UnhandledNullAsEmpty: allowUnhandled,
UnhandledUnknownAsEmpty: allowUnhandled,
})
}

// ToTerraformValue returns the data contained in the AttributeValue as
// a Go type that tftypes.NewValue will accept.
func (l *List) ToTerraformValue(ctx context.Context) (interface{}, error) {
if l.Unknown {
return tftypes.UnknownValue, nil
}
if l.Null {
return nil, nil
}
vals := make([]tftypes.Value, 0, len(l.Elems))
for _, elem := range l.Elems {
val, err := elem.ToTerraformValue(ctx)
if err != nil {
return nil, err
}
err = tftypes.ValidateValue(l.ElemType.TerraformType(ctx), val)
if err != nil {
return nil, err
}
vals = append(vals, tftypes.NewValue(l.ElemType.TerraformType(ctx), val))
}
return vals, nil
}

// Equal must return true if the AttributeValue is considered
// semantically equal to the AttributeValue passed as an argument.
func (l *List) Equal(o attr.Value) bool {
if l == nil {
return false
}
other, ok := o.(*List)
if !ok {
return false
}
if l.Unknown != other.Unknown {
return false
}
if l.Null != other.Null {
return false
}
if !l.ElemType.Equal(other.ElemType) {
return false
}
if len(l.Elems) != len(other.Elems) {
return false
}
for pos, lElem := range l.Elems {
oElem := other.Elems[pos]
if !lElem.Equal(oElem) {
return false
}
}
return true
}

// SetTerraformValue updates `l` to reflect the data stored in `in`.
func (l *List) SetTerraformValue(ctx context.Context, in tftypes.Value) error {
l.Unknown = false
l.Null = false
l.Elems = nil
if !in.Type().Is(tftypes.List{}) {
return fmt.Errorf("can't use %s as value of List, can only use tftypes.List values", in.String())
}
if !in.Type().Is(tftypes.List{ElementType: l.ElemType.TerraformType(ctx)}) {
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())
}
if !in.IsKnown() {
l.Unknown = true
return nil
}
if in.IsNull() {
l.Null = true
return nil
}
val := []tftypes.Value{}
err := in.As(&val)
if err != nil {
return err
}
elems := make([]attr.Value, 0, len(val))
for _, elem := range val {
av, err := l.ElemType.ValueFromTerraform(ctx, elem)
if err != nil {
return err
}
elems = append(elems, av)
}
l.Elems = elems
return nil
}
Loading