Skip to content

Commit

Permalink
feat: add set/list/mapValueOf[T]
Browse files Browse the repository at this point in the history
  • Loading branch information
azrod committed Jan 17, 2024
1 parent d70e64c commit 125dfa6
Show file tree
Hide file tree
Showing 15 changed files with 916 additions and 26 deletions.
25 changes: 25 additions & 0 deletions attrtypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,28 @@ func AttributeTypes[T any](ctx context.Context) (map[string]attr.Type, error) {
func AttributeTypesMust[T any](ctx context.Context) map[string]attr.Type {
return Must(AttributeTypes[T](ctx))
}

// ElementType returns the element type of the specified type T.
// T must be a slice or map and reflection is used to find the element type.
func ElementType[T any](_ context.Context) (attr.Type, error) {
var t T
val := reflect.ValueOf(t)
typ := val.Type()

switch typ.Kind() {
case reflect.String:
return StringType{}, nil
case reflect.Bool:
return BoolType{}, nil
case reflect.Int64:
return Int64Type{}, nil
case reflect.Float64:
return Float64Type{}, nil
default:
return nil, fmt.Errorf("%T has unsupported type: %s", t, typ)
}
}

func ElementTypeMust[T any](ctx context.Context) attr.Type {
return Must(ElementType[T](ctx))
}
52 changes: 52 additions & 0 deletions attrtypes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,55 @@ func TestAttributeTypes(t *testing.T) {
t.Errorf("unexpected diff (+wanted, -got): %s", diff)
}
}

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

type String string
type Int64 int64
type Bool bool
type Float64 float64
type Invalid int

ctx := context.Background()
got, err := supertypes.ElementType[String](ctx)
if err != nil {
t.Fatalf("unexpected error")
}

if diff := cmp.Diff(got, supertypes.StringType{}); diff != "" {
t.Errorf("unexpected diff (+wanted, -got): %s", diff)
}

got, err = supertypes.ElementType[Int64](ctx)
if err != nil {
t.Fatalf("unexpected error")
}

if diff := cmp.Diff(got, supertypes.Int64Type{}); diff != "" {
t.Errorf("unexpected diff (+wanted, -got): %s", diff)
}

got, err = supertypes.ElementType[Bool](ctx)
if err != nil {
t.Fatalf("unexpected error")
}

if diff := cmp.Diff(got, supertypes.BoolType{}); diff != "" {
t.Errorf("unexpected diff (+wanted, -got): %s", diff)
}

got, err = supertypes.ElementType[Float64](ctx)
if err != nil {
t.Fatalf("unexpected error")
}

if diff := cmp.Diff(got, supertypes.Float64Type{}); diff != "" {
t.Errorf("unexpected diff (+wanted, -got): %s", diff)
}

_, err = supertypes.ElementType[Invalid](ctx)
if err == nil {
t.Fatalf("expected error")
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.20
require (
github.com/fatih/color v1.16.0
github.com/google/go-cmp v0.6.0
github.com/hashicorp/terraform-plugin-framework v1.4.1
github.com/hashicorp/terraform-plugin-framework v1.4.2
github.com/hashicorp/terraform-plugin-go v0.19.1
github.com/iancoleman/strcase v0.3.0
github.com/stretchr/testify v1.8.4
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/terraform-plugin-framework v1.4.1 h1:ZC29MoB3Nbov6axHdgPbMz7799pT5H8kIrM8YAsaVrs=
github.com/hashicorp/terraform-plugin-framework v1.4.1/go.mod h1:XC0hPcQbBvlbxwmjxuV/8sn8SbZRg4XwGMs22f+kqV0=
github.com/hashicorp/terraform-plugin-framework v1.4.2 h1:P7a7VP1GZbjc4rv921Xy5OckzhoiO3ig6SGxwelD2sI=
github.com/hashicorp/terraform-plugin-framework v1.4.2/go.mod h1:GWl3InPFZi2wVQmdVnINPKys09s9mLmTZr95/ngLnbY=
github.com/hashicorp/terraform-plugin-go v0.19.1 h1:lf/jTGTeELcz5IIbn/94mJdmnTjRYm6S6ct/JqCSr50=
github.com/hashicorp/terraform-plugin-go v0.19.1/go.mod h1:5NMIS+DXkfacX6o5HCpswda5yjkSYfKzn1Nfl9l+qRs=
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
Expand Down
117 changes: 112 additions & 5 deletions list_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,33 @@ import (

// Ensure the implementation satisfies the expected interfaces.
var _ basetypes.ListTypable = ListType{}
var _ basetypes.ListTypable = ListTypeOf[struct{}]{}

// * ListType

type ListType struct {
basetypes.ListType
}

func (t ListType) Equal(o attr.Type) bool {
other, ok := o.(ListType)

if !ok {
switch o.(type) {
case ListType:
other, ok := o.(ListType)
if !ok {
return false
}

return t.ListType.Equal(other.ListType)
case basetypes.ObjectType:
other, ok := o.(basetypes.ObjectType)
if !ok {
return false
}

return t.ListType.Equal(other)
default:
return false
}

return t.ListType.Equal(other.ListType)
}

func (t ListType) String() string {
Expand Down Expand Up @@ -82,3 +96,96 @@ func (t ListType) ValueType(ctx context.Context) attr.Value {
ListValue: t.ListType.ValueType(ctx).(basetypes.ListValue),
}
}

// * -----------------
// * ListTypeOf is a type that implements ListTypable for a specific type.

type ListTypeOf[T any] struct {
basetypes.ListType
}

func NewListTypeOf[T any](ctx context.Context) ListTypeOf[T] {
return ListTypeOf[T]{ListType: basetypes.ListType{ElemType: ElementTypeMust[T](ctx)}}
}

// Equal returns true if the given type is equal to this type.
func (t ListTypeOf[T]) Equal(o attr.Type) bool {
switch o.(type) {
case ListTypeOf[T]:
other, ok := o.(ListTypeOf[T])
if !ok {
return false
}

return t.ListType.Equal(other.ListType)
case basetypes.ListType:
other, ok := o.(basetypes.ListType)
if !ok {
return false
}

return t.ListType.Equal(other)
default:
return false
}
}

// ValueFromList converts a basetypes.ListValue to a ListValueOf.
func (t ListTypeOf[T]) ValueFromList(ctx context.Context, in basetypes.ListValue) (basetypes.ListValuable, diag.Diagnostics) {
var diags diag.Diagnostics

if in.IsNull() {
return NewListValueOfNull[T](ctx), diags
}
if in.IsUnknown() {
return NewListValueOfUnknown[T](ctx), diags
}

v, d := basetypes.NewListValue(ElementTypeMust[T](ctx), in.Elements())
diags.Append(d...)
if diags.HasError() {
return NewListValueOfUnknown[T](ctx), diags
}

value := ListValueOf[T]{
ListValue: v,
}

return value, diags
}

// String returns a string representation of the type.
func (t ListTypeOf[T]) String() string {
var zero T
return fmt.Sprintf("supertypes.ListTypeOf[%T]", zero)
}

// ValueFromTerraform converts a tftypes.Value to a ListValueOf.
func (t ListTypeOf[T]) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
attrValue, err := t.ListType.ValueFromTerraform(ctx, in)
if err != nil {
return nil, err
}

ListValue, ok := attrValue.(basetypes.ListValue)
if !ok {
return nil, fmt.Errorf("unexpected value type of %T", attrValue)
}

ListValuable, diags := t.ValueFromList(ctx, ListValue)
if diags.HasError() {
return nil, fmt.Errorf("unexpected error converting ListValue to ListValuable: %v", diags)
}

return ListValuable, nil
}

// ValueType returns the value type of this List.
func (t ListTypeOf[T]) ValueType(ctx context.Context) attr.Value {
return ListValueOf[T]{}
}

// ElementType returns the element type of this List.
func (t ListTypeOf[T]) ElementType() attr.Type {
return ElementTypeMust[T](context.Background())
}
2 changes: 1 addition & 1 deletion list_type_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ func TestListTypeString(t *testing.T) {
},
"ElemType-missing": {
input: ListType{},
expected: "supertypes.ListType[!!! MISSING TYPE !!!]",
expected: "supertypes.ListType[!!! SUPER MISSING TYPE !!!]",
},
}

Expand Down
90 changes: 90 additions & 0 deletions list_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import (

// Ensure the implementation satisfies the expected interfaces.
var _ basetypes.ListValuable = ListValue{}
var _ basetypes.ListValuable = ListValueOf[struct{}]{}

// * ListType

type ListValue struct {
basetypes.ListValue
Expand Down Expand Up @@ -94,3 +97,90 @@ func (v *ListValue) SetUnknown(ctx context.Context) {
func (v ListValue) IsKnown() bool {
return !v.ListValue.IsNull() && !v.ListValue.IsUnknown()
}

// * ListTypeOf

type ListValueOf[T any] struct {
basetypes.ListValue
}

// ToListValue converts the given value to a ListValue.
func (v ListValueOf[T]) ToListValue(_ context.Context) (basetypes.ListValue, diag.Diagnostics) {
return v.ListValue, nil
}

// Equal returns true if the given value is equal to this value.
func (v ListValueOf[T]) Equal(o attr.Value) bool {
other, ok := o.(ListValueOf[T])

if !ok {
return false
}

return v.ListValue.Equal(other.ListValue)
}

// Type returns the type of this value.
func (v ListValueOf[T]) Type(ctx context.Context) attr.Type {
return NewListTypeOf[T](ctx)
}

// Get returns a ListValueOf from the given value.
func (v ListValueOf[T]) Get(ctx context.Context) (values []T, diags diag.Diagnostics) {

values = make([]T, len(v.ListValue.Elements()))

diags.Append(v.ListValue.ElementsAs(ctx, &values, false)...)
return
}

// Set sets the value of this value.
func (v *ListValueOf[T]) Set(ctx context.Context, elements []T) diag.Diagnostics {
var d diag.Diagnostics
v.ListValue, d = types.ListValueFrom(ctx, v.ElementType(ctx), elements)
return d
}

// NewListValueOfUnknown returns a new ListValueOf with an unknown value.
func NewListValueOfUnknown[T any](ctx context.Context) ListValueOf[T] {
return ListValueOf[T]{
ListValue: basetypes.NewListUnknown(ElementTypeMust[T](ctx)),
}
}

// NewListValueOfNull returns a new ListValueOf with a null value.
func NewListValueOfNull[T any](ctx context.Context) ListValueOf[T] {
return ListValueOf[T]{
ListValue: basetypes.NewListNull(ElementTypeMust[T](ctx)),
}
}

// newListValueOf is a helper function to create a new ListValueOf.
func newListValueOf[T any](ctx context.Context, elements any) ListValueOf[T] {
return ListValueOf[T]{ListValue: MustDiag(basetypes.NewListValueFrom(ctx, ElementTypeMust[T](ctx), elements))}
}

// NewListValueOfSlice returns a new ListValueOf with the given slice value.
func NewListValueOfSlice[T any](ctx context.Context, elements []T) ListValueOf[T] {
return newListValueOf[T](ctx, elements)
}

// NewListValueOfSlicePtr returns a new ListValueOf with the given slice value.
func NewListValueOfSlicePtr[T any](ctx context.Context, elements []*T) ListValueOf[T] {
return newListValueOf[T](ctx, elements)
}

// IsKnown returns true if the value is known.
func (v ListValueOf[T]) IsKnown() bool {
return !v.ListValue.IsNull() && !v.ListValue.IsUnknown()
}

// SetNull sets the value to null.
func (v *ListValueOf[T]) SetNull(ctx context.Context) {
(*v) = NewListValueOfNull[T](ctx)
}

// SetUnknown sets the value to unknown.
func (v *ListValueOf[T]) SetUnknown(ctx context.Context) {
(*v) = NewListValueOfUnknown[T](ctx)
}
Loading

0 comments on commit 125dfa6

Please sign in to comment.