From 125dfa67cb225543b46d0f8bff05be4556e2266d Mon Sep 17 00:00:00 2001 From: Mickael Stanislas Date: Wed, 17 Jan 2024 11:22:38 +0100 Subject: [PATCH] feat: add set/list/mapValueOf[T] --- attrtypes.go | 25 +++++++ attrtypes_test.go | 52 +++++++++++++++ go.mod | 2 +- go.sum | 4 +- list_type.go | 117 +++++++++++++++++++++++++++++++-- list_type_test.go | 2 +- list_value.go | 90 +++++++++++++++++++++++++ map_type.go | 117 +++++++++++++++++++++++++++++++-- map_type_test.go | 2 +- map_value.go | 90 +++++++++++++++++++++++++ missing.go | 2 +- set_type.go | 117 +++++++++++++++++++++++++++++++-- set_value.go | 90 +++++++++++++++++++++++++ templates/array_type.go.tmpl | 119 ++++++++++++++++++++++++++++++++-- templates/array_value.go.tmpl | 113 ++++++++++++++++++++++++++++++++ 15 files changed, 916 insertions(+), 26 deletions(-) diff --git a/attrtypes.go b/attrtypes.go index 035df7c..4e30156 100644 --- a/attrtypes.go +++ b/attrtypes.go @@ -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)) +} diff --git a/attrtypes_test.go b/attrtypes_test.go index 91f0b94..f67f248 100644 --- a/attrtypes_test.go +++ b/attrtypes_test.go @@ -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") + } +} diff --git a/go.mod b/go.mod index 2c8b596..8d8f66c 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index fa37f8f..8016a30 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/list_type.go b/list_type.go index fc2b499..17c8a2d 100644 --- a/list_type.go +++ b/list_type.go @@ -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 { @@ -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()) +} diff --git a/list_type_test.go b/list_type_test.go index 218d3a7..c44caa0 100644 --- a/list_type_test.go +++ b/list_type_test.go @@ -336,7 +336,7 @@ func TestListTypeString(t *testing.T) { }, "ElemType-missing": { input: ListType{}, - expected: "supertypes.ListType[!!! MISSING TYPE !!!]", + expected: "supertypes.ListType[!!! SUPER MISSING TYPE !!!]", }, } diff --git a/list_value.go b/list_value.go index d02d9df..e1b9652 100644 --- a/list_value.go +++ b/list_value.go @@ -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 @@ -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) +} diff --git a/map_type.go b/map_type.go index 7b83af2..e0281b1 100644 --- a/map_type.go +++ b/map_type.go @@ -15,19 +15,33 @@ import ( // Ensure the implementation satisfies the expected interfaces. var _ basetypes.MapTypable = MapType{} +var _ basetypes.MapTypable = MapTypeOf[struct{}]{} + +// * MapType type MapType struct { basetypes.MapType } func (t MapType) Equal(o attr.Type) bool { - other, ok := o.(MapType) - - if !ok { + switch o.(type) { + case MapType: + other, ok := o.(MapType) + if !ok { + return false + } + + return t.MapType.Equal(other.MapType) + case basetypes.ObjectType: + other, ok := o.(basetypes.ObjectType) + if !ok { + return false + } + + return t.MapType.Equal(other) + default: return false } - - return t.MapType.Equal(other.MapType) } func (t MapType) String() string { @@ -82,3 +96,96 @@ func (t MapType) ValueType(ctx context.Context) attr.Value { MapValue: t.MapType.ValueType(ctx).(basetypes.MapValue), } } + +// * ----------------- +// * MapTypeOf is a type that implements MapTypable for a specific type. + +type MapTypeOf[T any] struct { + basetypes.MapType +} + +func NewMapTypeOf[T any](ctx context.Context) MapTypeOf[T] { + return MapTypeOf[T]{MapType: basetypes.MapType{ElemType: ElementTypeMust[T](ctx)}} +} + +// Equal returns true if the given type is equal to this type. +func (t MapTypeOf[T]) Equal(o attr.Type) bool { + switch o.(type) { + case MapTypeOf[T]: + other, ok := o.(MapTypeOf[T]) + if !ok { + return false + } + + return t.MapType.Equal(other.MapType) + case basetypes.MapType: + other, ok := o.(basetypes.MapType) + if !ok { + return false + } + + return t.MapType.Equal(other) + default: + return false + } +} + +// ValueFromMap converts a basetypes.MapValue to a MapValueOf. +func (t MapTypeOf[T]) ValueFromMap(ctx context.Context, in basetypes.MapValue) (basetypes.MapValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + if in.IsNull() { + return NewMapValueOfNull[T](ctx), diags + } + if in.IsUnknown() { + return NewMapValueOfUnknown[T](ctx), diags + } + + v, d := basetypes.NewMapValue(ElementTypeMust[T](ctx), in.Elements()) + diags.Append(d...) + if diags.HasError() { + return NewMapValueOfUnknown[T](ctx), diags + } + + value := MapValueOf[T]{ + MapValue: v, + } + + return value, diags +} + +// String returns a string representation of the type. +func (t MapTypeOf[T]) String() string { + var zero T + return fmt.Sprintf("supertypes.MapTypeOf[%T]", zero) +} + +// ValueFromTerraform converts a tftypes.Value to a MapValueOf. +func (t MapTypeOf[T]) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + attrValue, err := t.MapType.ValueFromTerraform(ctx, in) + if err != nil { + return nil, err + } + + MapValue, ok := attrValue.(basetypes.MapValue) + if !ok { + return nil, fmt.Errorf("unexpected value type of %T", attrValue) + } + + MapValuable, diags := t.ValueFromMap(ctx, MapValue) + if diags.HasError() { + return nil, fmt.Errorf("unexpected error converting MapValue to MapValuable: %v", diags) + } + + return MapValuable, nil +} + +// ValueType returns the value type of this Map. +func (t MapTypeOf[T]) ValueType(ctx context.Context) attr.Value { + return MapValueOf[T]{} +} + +// ElementType returns the element type of this Map. +func (t MapTypeOf[T]) ElementType() attr.Type { + return ElementTypeMust[T](context.Background()) +} diff --git a/map_type_test.go b/map_type_test.go index 247bc59..fd9545f 100644 --- a/map_type_test.go +++ b/map_type_test.go @@ -334,7 +334,7 @@ func TestMapTypeString(t *testing.T) { }, "ElemType-missing": { input: MapType{}, - expected: "supertypes.MapType[!!! MISSING TYPE !!!]", + expected: "supertypes.MapType[!!! SUPER MISSING TYPE !!!]", }, } diff --git a/map_value.go b/map_value.go index ecf16b1..9e38707 100644 --- a/map_value.go +++ b/map_value.go @@ -14,6 +14,9 @@ import ( // Ensure the implementation satisfies the expected interfaces. var _ basetypes.MapValuable = MapValue{} +var _ basetypes.MapValuable = MapValueOf[struct{}]{} + +// * MapType type MapValue struct { basetypes.MapValue @@ -94,3 +97,90 @@ func (v *MapValue) SetUnknown(ctx context.Context) { func (v MapValue) IsKnown() bool { return !v.MapValue.IsNull() && !v.MapValue.IsUnknown() } + +// * MapTypeOf + +type MapValueOf[T any] struct { + basetypes.MapValue +} + +// ToMapValue converts the given value to a MapValue. +func (v MapValueOf[T]) ToMapValue(_ context.Context) (basetypes.MapValue, diag.Diagnostics) { + return v.MapValue, nil +} + +// Equal returns true if the given value is equal to this value. +func (v MapValueOf[T]) Equal(o attr.Value) bool { + other, ok := o.(MapValueOf[T]) + + if !ok { + return false + } + + return v.MapValue.Equal(other.MapValue) +} + +// Type returns the type of this value. +func (v MapValueOf[T]) Type(ctx context.Context) attr.Type { + return NewMapTypeOf[T](ctx) +} + +// Get returns a MapValueOf from the given value. +func (v MapValueOf[T]) Get(ctx context.Context) (values map[string]T, diags diag.Diagnostics) { + + values = make(map[string]T, len(v.MapValue.Elements())) + + diags.Append(v.MapValue.ElementsAs(ctx, &values, false)...) + return +} + +// Set sets the value of this value. +func (v *MapValueOf[T]) Set(ctx context.Context, elements map[string]T) diag.Diagnostics { + var d diag.Diagnostics + v.MapValue, d = types.MapValueFrom(ctx, v.ElementType(ctx), elements) + return d +} + +// NewMapValueOfUnknown returns a new MapValueOf with an unknown value. +func NewMapValueOfUnknown[T any](ctx context.Context) MapValueOf[T] { + return MapValueOf[T]{ + MapValue: basetypes.NewMapUnknown(ElementTypeMust[T](ctx)), + } +} + +// NewMapValueOfNull returns a new MapValueOf with a null value. +func NewMapValueOfNull[T any](ctx context.Context) MapValueOf[T] { + return MapValueOf[T]{ + MapValue: basetypes.NewMapNull(ElementTypeMust[T](ctx)), + } +} + +// newMapValueOf is a helper function to create a new MapValueOf. +func newMapValueOf[T any](ctx context.Context, elements any) MapValueOf[T] { + return MapValueOf[T]{ + MapValue: MustDiag(types.MapValueFrom(ctx, ElementTypeMust[T](ctx), elements)), + } +} + +// NewMapValueOfMap returns a new MapValueOf with the given map value. +func NewMapValueOfMap[T any](ctx context.Context, elements map[string]T) (MapValueOf[T], diag.Diagnostics) { + v, d := types.MapValueFrom(ctx, ElementTypeMust[T](ctx), elements) + return MapValueOf[T]{ + MapValue: v, + }, d +} + +// IsKnown returns true if the value is known. +func (v MapValueOf[T]) IsKnown() bool { + return !v.MapValue.IsNull() && !v.MapValue.IsUnknown() +} + +// SetNull sets the value to null. +func (v *MapValueOf[T]) SetNull(ctx context.Context) { + (*v) = NewMapValueOfNull[T](ctx) +} + +// SetUnknown sets the value to unknown. +func (v *MapValueOf[T]) SetUnknown(ctx context.Context) { + (*v) = NewMapValueOfUnknown[T](ctx) +} diff --git a/missing.go b/missing.go index d07e8ec..3209b0d 100644 --- a/missing.go +++ b/missing.go @@ -33,7 +33,7 @@ func (t missingType) Equal(o attr.Type) bool { // String returns a human readable string of the type. func (t missingType) String() string { - return "!!! MISSING TYPE !!!" + return "!!! SUPER MISSING TYPE !!!" } // TerraformType returns DynamicPseudoType. diff --git a/set_type.go b/set_type.go index 697ebe5..a8d8884 100644 --- a/set_type.go +++ b/set_type.go @@ -15,19 +15,33 @@ import ( // Ensure the implementation satisfies the expected interfaces. var _ basetypes.SetTypable = SetType{} +var _ basetypes.SetTypable = SetTypeOf[struct{}]{} + +// * SetType type SetType struct { basetypes.SetType } func (t SetType) Equal(o attr.Type) bool { - other, ok := o.(SetType) - - if !ok { + switch o.(type) { + case SetType: + other, ok := o.(SetType) + if !ok { + return false + } + + return t.SetType.Equal(other.SetType) + case basetypes.ObjectType: + other, ok := o.(basetypes.ObjectType) + if !ok { + return false + } + + return t.SetType.Equal(other) + default: return false } - - return t.SetType.Equal(other.SetType) } func (t SetType) String() string { @@ -82,3 +96,96 @@ func (t SetType) ValueType(ctx context.Context) attr.Value { SetValue: t.SetType.ValueType(ctx).(basetypes.SetValue), } } + +// * ----------------- +// * SetTypeOf is a type that implements SetTypable for a specific type. + +type SetTypeOf[T any] struct { + basetypes.SetType +} + +func NewSetTypeOf[T any](ctx context.Context) SetTypeOf[T] { + return SetTypeOf[T]{SetType: basetypes.SetType{ElemType: ElementTypeMust[T](ctx)}} +} + +// Equal returns true if the given type is equal to this type. +func (t SetTypeOf[T]) Equal(o attr.Type) bool { + switch o.(type) { + case SetTypeOf[T]: + other, ok := o.(SetTypeOf[T]) + if !ok { + return false + } + + return t.SetType.Equal(other.SetType) + case basetypes.SetType: + other, ok := o.(basetypes.SetType) + if !ok { + return false + } + + return t.SetType.Equal(other) + default: + return false + } +} + +// ValueFromSet converts a basetypes.SetValue to a SetValueOf. +func (t SetTypeOf[T]) ValueFromSet(ctx context.Context, in basetypes.SetValue) (basetypes.SetValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + if in.IsNull() { + return NewSetValueOfNull[T](ctx), diags + } + if in.IsUnknown() { + return NewSetValueOfUnknown[T](ctx), diags + } + + v, d := basetypes.NewSetValue(ElementTypeMust[T](ctx), in.Elements()) + diags.Append(d...) + if diags.HasError() { + return NewSetValueOfUnknown[T](ctx), diags + } + + value := SetValueOf[T]{ + SetValue: v, + } + + return value, diags +} + +// String returns a string representation of the type. +func (t SetTypeOf[T]) String() string { + var zero T + return fmt.Sprintf("supertypes.SetTypeOf[%T]", zero) +} + +// ValueFromTerraform converts a tftypes.Value to a SetValueOf. +func (t SetTypeOf[T]) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + attrValue, err := t.SetType.ValueFromTerraform(ctx, in) + if err != nil { + return nil, err + } + + SetValue, ok := attrValue.(basetypes.SetValue) + if !ok { + return nil, fmt.Errorf("unexpected value type of %T", attrValue) + } + + SetValuable, diags := t.ValueFromSet(ctx, SetValue) + if diags.HasError() { + return nil, fmt.Errorf("unexpected error converting SetValue to SetValuable: %v", diags) + } + + return SetValuable, nil +} + +// ValueType returns the value type of this Set. +func (t SetTypeOf[T]) ValueType(ctx context.Context) attr.Value { + return SetValueOf[T]{} +} + +// ElementType returns the element type of this Set. +func (t SetTypeOf[T]) ElementType() attr.Type { + return ElementTypeMust[T](context.Background()) +} diff --git a/set_value.go b/set_value.go index c145c23..f8c092b 100644 --- a/set_value.go +++ b/set_value.go @@ -14,6 +14,9 @@ import ( // Ensure the implementation satisfies the expected interfaces. var _ basetypes.SetValuable = SetValue{} +var _ basetypes.SetValuable = SetValueOf[struct{}]{} + +// * SetType type SetValue struct { basetypes.SetValue @@ -94,3 +97,90 @@ func (v *SetValue) SetUnknown(ctx context.Context) { func (v SetValue) IsKnown() bool { return !v.SetValue.IsNull() && !v.SetValue.IsUnknown() } + +// * SetTypeOf + +type SetValueOf[T any] struct { + basetypes.SetValue +} + +// ToSetValue converts the given value to a SetValue. +func (v SetValueOf[T]) ToSetValue(_ context.Context) (basetypes.SetValue, diag.Diagnostics) { + return v.SetValue, nil +} + +// Equal returns true if the given value is equal to this value. +func (v SetValueOf[T]) Equal(o attr.Value) bool { + other, ok := o.(SetValueOf[T]) + + if !ok { + return false + } + + return v.SetValue.Equal(other.SetValue) +} + +// Type returns the type of this value. +func (v SetValueOf[T]) Type(ctx context.Context) attr.Type { + return NewSetTypeOf[T](ctx) +} + +// Get returns a SetValueOf from the given value. +func (v SetValueOf[T]) Get(ctx context.Context) (values []T, diags diag.Diagnostics) { + + values = make([]T, len(v.SetValue.Elements())) + + diags.Append(v.SetValue.ElementsAs(ctx, &values, false)...) + return +} + +// Set sets the value of this value. +func (v *SetValueOf[T]) Set(ctx context.Context, elements []T) diag.Diagnostics { + var d diag.Diagnostics + v.SetValue, d = types.SetValueFrom(ctx, v.ElementType(ctx), elements) + return d +} + +// NewSetValueOfUnknown returns a new SetValueOf with an unknown value. +func NewSetValueOfUnknown[T any](ctx context.Context) SetValueOf[T] { + return SetValueOf[T]{ + SetValue: basetypes.NewSetUnknown(ElementTypeMust[T](ctx)), + } +} + +// NewSetValueOfNull returns a new SetValueOf with a null value. +func NewSetValueOfNull[T any](ctx context.Context) SetValueOf[T] { + return SetValueOf[T]{ + SetValue: basetypes.NewSetNull(ElementTypeMust[T](ctx)), + } +} + +// newSetValueOf is a helper function to create a new SetValueOf. +func newSetValueOf[T any](ctx context.Context, elements any) SetValueOf[T] { + return SetValueOf[T]{SetValue: MustDiag(basetypes.NewSetValueFrom(ctx, ElementTypeMust[T](ctx), elements))} +} + +// NewSetValueOfSlice returns a new SetValueOf with the given slice value. +func NewSetValueOfSlice[T any](ctx context.Context, elements []T) SetValueOf[T] { + return newSetValueOf[T](ctx, elements) +} + +// NewSetValueOfSlicePtr returns a new SetValueOf with the given slice value. +func NewSetValueOfSlicePtr[T any](ctx context.Context, elements []*T) SetValueOf[T] { + return newSetValueOf[T](ctx, elements) +} + +// IsKnown returns true if the value is known. +func (v SetValueOf[T]) IsKnown() bool { + return !v.SetValue.IsNull() && !v.SetValue.IsUnknown() +} + +// SetNull sets the value to null. +func (v *SetValueOf[T]) SetNull(ctx context.Context) { + (*v) = NewSetValueOfNull[T](ctx) +} + +// SetUnknown sets the value to unknown. +func (v *SetValueOf[T]) SetUnknown(ctx context.Context) { + (*v) = NewSetValueOfUnknown[T](ctx) +} diff --git a/templates/array_type.go.tmpl b/templates/array_type.go.tmpl index 0ef3acf..6f89547 100644 --- a/templates/array_type.go.tmpl +++ b/templates/array_type.go.tmpl @@ -13,19 +13,33 @@ import ( // Ensure the implementation satisfies the expected interfaces. var _ basetypes.{{ .TypeName }}Typable = {{ .TypeName }}Type{} +var _ basetypes.{{ .TypeName }}Typable = {{ .TypeName }}TypeOf[struct{}]{} + +// * {{ .TypeName }}Type type {{ .TypeName }}Type struct { basetypes.{{ .TypeName }}Type } func (t {{ .TypeName }}Type) Equal(o attr.Type) bool { - other, ok := o.({{ .TypeName }}Type) - - if !ok { + switch o.(type) { + case {{ .TypeName }}Type: + other, ok := o.({{ .TypeName }}Type) + if !ok { + return false + } + + return t.{{ .TypeName }}Type.Equal(other.{{ .TypeName }}Type) + case basetypes.ObjectType: + other, ok := o.(basetypes.ObjectType) + if !ok { + return false + } + + return t.{{ .TypeName }}Type.Equal(other) + default: return false } - - return t.{{ .TypeName }}Type.Equal(other.{{ .TypeName }}Type) } func (t {{ .TypeName }}Type) String() string { @@ -80,3 +94,98 @@ func (t {{ .TypeName }}Type) ValueType(ctx context.Context) attr.Value { {{ .TypeName }}Value: t.{{ .TypeName }}Type.ValueType(ctx).(basetypes.{{ .TypeName }}Value), } } + +// * ----------------- +// * {{ .TypeName }}TypeOf is a type that implements {{ .TypeName }}Typable for a specific type. + +type {{ .TypeName }}TypeOf[T any] struct { + basetypes.{{ .TypeName }}Type +} + +func New{{ .TypeName }}TypeOf[T any](ctx context.Context) {{ .TypeName }}TypeOf[T] { + return {{ .TypeName }}TypeOf[T]{ {{ .TypeName }}Type: basetypes.{{ .TypeName }}Type{ElemType: ElementTypeMust[T](ctx)}} +} + +// Equal returns true if the given type is equal to this type. +func (t {{ .TypeName }}TypeOf[T]) Equal(o attr.Type) bool { + switch o.(type) { + case {{ .TypeName }}TypeOf[T]: + other, ok := o.({{ .TypeName }}TypeOf[T]) + if !ok { + return false + } + + return t.{{ .TypeName }}Type.Equal(other.{{ .TypeName }}Type) + case basetypes.{{ .TypeName }}Type: + other, ok := o.(basetypes.{{ .TypeName }}Type) + if !ok { + return false + } + + return t.{{ .TypeName }}Type.Equal(other) + default: + return false + } +} + + +// ValueFrom{{ .TypeName }} converts a basetypes.{{ .TypeName }}Value to a {{ .TypeName }}ValueOf. +func (t {{ .TypeName }}TypeOf[T]) ValueFrom{{ .TypeName }}(ctx context.Context, in basetypes.{{ .TypeName }}Value) (basetypes.{{ .TypeName }}Valuable, diag.Diagnostics) { + var diags diag.Diagnostics + + if in.IsNull() { + return New{{ .TypeName }}ValueOfNull[T](ctx), diags + } + if in.IsUnknown() { + return New{{ .TypeName }}ValueOfUnknown[T](ctx), diags + } + + v, d := basetypes.New{{ .TypeName }}Value(ElementTypeMust[T](ctx), in.Elements()) + diags.Append(d...) + if diags.HasError() { + return New{{ .TypeName }}ValueOfUnknown[T](ctx), diags + } + + value := {{ .TypeName }}ValueOf[T]{ + {{ .TypeName }}Value: v, + } + + return value, diags +} + +// String returns a string representation of the type. +func (t {{ .TypeName }}TypeOf[T]) String() string { + var zero T + return fmt.Sprintf("supertypes.{{ .TypeName }}TypeOf[%T]", zero) +} + + +// ValueFromTerraform converts a tftypes.Value to a {{ .TypeName }}ValueOf. +func (t {{ .TypeName }}TypeOf[T]) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + attrValue, err := t.{{ .TypeName }}Type.ValueFromTerraform(ctx, in) + if err != nil { + return nil, err + } + + {{ .TypeName }}Value, ok := attrValue.(basetypes.{{ .TypeName }}Value) + if !ok { + return nil, fmt.Errorf("unexpected value type of %T", attrValue) + } + + {{ .TypeName }}Valuable, diags := t.ValueFrom{{ .TypeName }}(ctx, {{ .TypeName }}Value) + if diags.HasError() { + return nil, fmt.Errorf("unexpected error converting {{ .TypeName }}Value to {{ .TypeName }}Valuable: %v", diags) + } + + return {{ .TypeName }}Valuable, nil +} + +// ValueType returns the value type of this {{ .TypeName }}. +func (t {{ .TypeName }}TypeOf[T]) ValueType(ctx context.Context) attr.Value { + return {{ .TypeName }}ValueOf[T]{} +} + +// ElementType returns the element type of this {{ .TypeName }}. +func (t {{ .TypeName }}TypeOf[T]) ElementType() attr.Type { + return ElementTypeMust[T](context.Background()) +} diff --git a/templates/array_value.go.tmpl b/templates/array_value.go.tmpl index f6787f7..94983b4 100644 --- a/templates/array_value.go.tmpl +++ b/templates/array_value.go.tmpl @@ -12,6 +12,10 @@ import ( // Ensure the implementation satisfies the expected interfaces. var _ basetypes.{{ .TypeName }}Valuable = {{ .TypeName }}Value{} +var _ basetypes.{{ .TypeName }}Valuable = {{ .TypeName }}ValueOf[struct{}]{} + +// * {{ .TypeName }}Type + type {{ .TypeName }}Value struct { basetypes.{{ .TypeName }}Value @@ -92,3 +96,112 @@ func (v *{{ .TypeName }}Value) SetUnknown(ctx context.Context) { func (v {{ .TypeName }}Value) IsKnown() bool { return !v.{{ .TypeName }}Value.IsNull() && !v.{{ .TypeName }}Value.IsUnknown() } + +// * {{ .TypeName }}TypeOf + +type {{ .TypeName }}ValueOf[T any] struct { + basetypes.{{ .TypeName }}Value +} + +// To{{ .TypeName }}Value converts the given value to a {{ .TypeName }}Value. +func (v {{ .TypeName }}ValueOf[T]) To{{ .TypeName }}Value(_ context.Context) (basetypes.{{ .TypeName }}Value, diag.Diagnostics) { + return v.{{ .TypeName }}Value, nil +} + +// Equal returns true if the given value is equal to this value. +func (v {{ .TypeName }}ValueOf[T]) Equal(o attr.Value) bool { + other, ok := o.({{ .TypeName }}ValueOf[T]) + + if !ok { + return false + } + + return v.{{ .TypeName }}Value.Equal(other.{{ .TypeName }}Value) +} + +// Type returns the type of this value. +func (v {{ .TypeName }}ValueOf[T]) Type(ctx context.Context) attr.Type { + return New{{ .TypeName }}TypeOf[T](ctx) +} + +// Get returns a {{ .TypeName }}ValueOf from the given value. +func (v {{ .TypeName }}ValueOf[T]) Get(ctx context.Context) (values {{ if eq .TypeName "Map"}}map[string]T{{else}}[]T{{end}}, diags diag.Diagnostics) { + {{ if eq .TypeName "Map"}} + values = make(map[string]T, len(v.{{ .TypeName }}Value.Elements())) + {{ else }} + values = make([]T, len(v.{{ .TypeName }}Value.Elements())) + {{ end }} + + diags.Append(v.{{ .TypeName }}Value.ElementsAs(ctx, &values, false)...) + return +} + +// Set sets the value of this value. +func (v *{{ .TypeName }}ValueOf[T]) Set(ctx context.Context, elements {{ if eq .TypeName "Map"}}map[string]T{{else}}[]T{{end}}) diag.Diagnostics { + var d diag.Diagnostics + v.{{ .TypeName }}Value, d = types.{{ .TypeName }}ValueFrom(ctx, v.ElementType(ctx), elements) + return d +} + +// New{{ .TypeName }}ValueOfUnknown returns a new {{ .TypeName }}ValueOf with an unknown value. +func New{{ .TypeName }}ValueOfUnknown[T any](ctx context.Context) {{ .TypeName }}ValueOf[T] { + return {{ .TypeName }}ValueOf[T]{ + {{ .TypeName }}Value: basetypes.New{{ .TypeName }}Unknown(ElementTypeMust[T](ctx)), + } +} + +// New{{ .TypeName }}ValueOfNull returns a new {{ .TypeName }}ValueOf with a null value. +func New{{ .TypeName }}ValueOfNull[T any](ctx context.Context) {{ .TypeName }}ValueOf[T] { + return {{ .TypeName }}ValueOf[T]{ + {{ .TypeName }}Value: basetypes.New{{ .TypeName }}Null(ElementTypeMust[T](ctx)), + } +} + +{{ if ne .TypeName "Map"}} +// new{{ .TypeName }}ValueOf is a helper function to create a new {{ .TypeName }}ValueOf. +func new{{ .TypeName }}ValueOf[T any](ctx context.Context, elements any) {{ .TypeName }}ValueOf[T] { + return {{ .TypeName }}ValueOf[T]{ {{ .TypeName }}Value: MustDiag(basetypes.New{{ .TypeName }}ValueFrom(ctx, ElementTypeMust[T](ctx), elements))} +} + +// New{{ .TypeName }}ValueOfSlice returns a new {{ .TypeName }}ValueOf with the given slice value. +func New{{ .TypeName }}ValueOfSlice[T any](ctx context.Context, elements []T) {{ .TypeName }}ValueOf[T] { + return new{{ .TypeName }}ValueOf[T](ctx, elements) +} + +// New{{ .TypeName }}ValueOfSlicePtr returns a new {{ .TypeName }}ValueOf with the given slice value. +func New{{ .TypeName }}ValueOfSlicePtr[T any](ctx context.Context, elements []*T) {{ .TypeName }}ValueOf[T] { + return new{{ .TypeName }}ValueOf[T](ctx, elements) +} +{{ end }} + +{{ if eq .TypeName "Map"}} +// new{{ .TypeName }}ValueOf is a helper function to create a new {{ .TypeName }}ValueOf. +func new{{ .TypeName }}ValueOf[T any](ctx context.Context, elements any) {{ .TypeName }}ValueOf[T] { + return {{ .TypeName }}ValueOf[T]{ + {{ .TypeName }}Value: MustDiag(types.{{ .TypeName }}ValueFrom(ctx, ElementTypeMust[T](ctx), elements)), + } +} + +// New{{ .TypeName }}ValueOfMap returns a new {{ .TypeName }}ValueOf with the given map value. +func New{{ .TypeName }}ValueOfMap[T any](ctx context.Context, elements map[string]T) ({{ .TypeName }}ValueOf[T], diag.Diagnostics) { + v, d := types.{{ .TypeName }}ValueFrom(ctx, ElementTypeMust[T](ctx), elements) + return {{ .TypeName }}ValueOf[T]{ + {{ .TypeName }}Value: v, + }, d +} +{{ end }} + +// IsKnown returns true if the value is known. +func (v {{ .TypeName }}ValueOf[T]) IsKnown() bool { + return !v.{{ .TypeName }}Value.IsNull() && !v.{{ .TypeName }}Value.IsUnknown() +} + +// SetNull sets the value to null. +func (v *{{ .TypeName }}ValueOf[T]) SetNull(ctx context.Context) { + (*v) = New{{ .TypeName }}ValueOfNull[T](ctx) +} + +// SetUnknown sets the value to unknown. +func (v *{{ .TypeName }}ValueOf[T]) SetUnknown(ctx context.Context) { + (*v) = New{{ .TypeName }}ValueOfUnknown[T](ctx) +} \ No newline at end of file