diff --git a/.changelog/231.txt b/.changelog/231.txt new file mode 100644 index 000000000..4840cec1d --- /dev/null +++ b/.changelog/231.txt @@ -0,0 +1,3 @@ +```release-note:breaking-change +The `ToTerraformValue` method of the `attr.Value` interface now returns a `tftypes.Value`, instead of an `interface{}`. Existing types need to be updated to call `tftypes.ValidateValue` and `tftypes.NewValue`, passing the value they were returning before, instead of returning the value directly. +``` diff --git a/attr/value.go b/attr/value.go index c0f6794ee..3b151a4f3 100644 --- a/attr/value.go +++ b/attr/value.go @@ -2,6 +2,8 @@ package attr import ( "context" + + "github.com/hashicorp/terraform-plugin-go/tftypes" ) // Value defines an interface for describing data associated with an attribute. @@ -12,8 +14,8 @@ type Value interface { Type(context.Context) Type // ToTerraformValue returns the data contained in the Value as - // a Go type that tftypes.NewValue will accept. - ToTerraformValue(context.Context) (interface{}, error) + // a tftypes.Value. + ToTerraformValue(context.Context) (tftypes.Value, error) // Equal must return true if the Value is considered semantically equal // to the Value passed as an argument. diff --git a/internal/reflect/interfaces.go b/internal/reflect/interfaces.go index 18f40b29f..405640ac8 100644 --- a/internal/reflect/interfaces.go +++ b/internal/reflect/interfaces.go @@ -311,14 +311,12 @@ func FromAttributeValue(ctx context.Context, typ attr.Type, val attr.Value, path var diags diag.Diagnostics if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - tfType := typ.TerraformType(ctx) tfVal, err := val.ToTerraformValue(ctx) - if err != nil { return val, append(diags, toTerraformValueErrorDiag(err, path)) } - diags.Append(typeWithValidate.Validate(ctx, tftypes.NewValue(tfType, tfVal), path)...) + diags.Append(typeWithValidate.Validate(ctx, tfVal, path)...) if diags.HasError() { return val, diags diff --git a/internal/reflect/map.go b/internal/reflect/map.go index c780be541..2dae10a70 100644 --- a/internal/reflect/map.go +++ b/internal/reflect/map.go @@ -143,24 +143,15 @@ func FromMap(ctx context.Context, typ attr.TypeWithElementType, val reflect.Valu return nil, append(diags, toTerraformValueErrorDiag(err, path)) } - tfElemType := elemType.TerraformType(ctx) - err = tftypes.ValidateValue(tfElemType, tfVal) - - if err != nil { - return nil, append(diags, validateValueErrorDiag(err, path)) - } - - tfElemVal := tftypes.NewValue(tfElemType, tfVal) - if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { - diags.Append(typeWithValidate.Validate(ctx, tfElemVal, path.WithElementKeyString(key.String()))...) + diags.Append(typeWithValidate.Validate(ctx, tfVal, path.WithElementKeyString(key.String()))...) if diags.HasError() { return nil, diags } } - tfElems[key.String()] = tfElemVal + tfElems[key.String()] = tfVal } err := tftypes.ValidateValue(tfType, tfElems) diff --git a/internal/reflect/slice.go b/internal/reflect/slice.go index d9359a02a..4b5c1198d 100644 --- a/internal/reflect/slice.go +++ b/internal/reflect/slice.go @@ -151,32 +151,22 @@ func FromSlice(ctx context.Context, typ attr.Type, val reflect.Value, path *tfty } tfVal, err := val.ToTerraformValue(ctx) - if err != nil { return nil, append(diags, toTerraformValueErrorDiag(err, path)) } - err = tftypes.ValidateValue(elemType.TerraformType(ctx), tfVal) - - if err != nil { - return nil, append(diags, validateValueErrorDiag(err, path)) - } - - tfElemVal := tftypes.NewValue(elemType.TerraformType(ctx), tfVal) - if tfType.Is(tftypes.Set{}) { - valPath = path.WithElementKeyValue(tfElemVal) + valPath = path.WithElementKeyValue(tfVal) } if typeWithValidate, ok := elemType.(attr.TypeWithValidate); ok { - diags.Append(typeWithValidate.Validate(ctx, tfElemVal, valPath)...) - + diags.Append(typeWithValidate.Validate(ctx, tfVal, valPath)...) if diags.HasError() { return nil, diags } } - tfElems = append(tfElems, tfElemVal) + tfElems = append(tfElems, tfVal) } err := tftypes.ValidateValue(tfType, tfElems) diff --git a/internal/reflect/struct.go b/internal/reflect/struct.go index 5ea3c206d..80adf0238 100644 --- a/internal/reflect/struct.go +++ b/internal/reflect/struct.go @@ -186,16 +186,10 @@ func FromStruct(ctx context.Context, typ attr.TypeWithAttributeTypes, val reflec objTypes[name] = attrType.TerraformType(ctx) - tfVal, err := attrVal.ToTerraformValue(ctx) + tfObjVal, err := attrVal.ToTerraformValue(ctx) if err != nil { return nil, append(diags, toTerraformValueErrorDiag(err, path)) } - err = tftypes.ValidateValue(objTypes[name], tfVal) - if err != nil { - return nil, append(diags, validateValueErrorDiag(err, path)) - } - - tfObjVal := tftypes.NewValue(objTypes[name], tfVal) if typeWithValidate, ok := typ.(attr.TypeWithValidate); ok { diags.Append(typeWithValidate.Validate(ctx, tfObjVal, path)...) diff --git a/tfsdk/attribute.go b/tfsdk/attribute.go index c5ee5c500..90e0b943a 100644 --- a/tfsdk/attribute.go +++ b/tfsdk/attribute.go @@ -305,7 +305,6 @@ func (a Attribute) validate(ctx context.Context, req ValidateAttributeRequest, r if a.DeprecationMessage != "" && attributeConfig != nil { tfValue, err := attributeConfig.ToTerraformValue(ctx) - if err != nil { resp.Diagnostics.AddAttributeError( req.AttributePath, @@ -316,7 +315,7 @@ func (a Attribute) validate(ctx context.Context, req ValidateAttributeRequest, r return } - if tfValue != nil { + if !tfValue.IsNull() { resp.Diagnostics.AddAttributeWarning( req.AttributePath, "Attribute Deprecated", @@ -378,8 +377,7 @@ func (a Attribute) validateAttributes(ctx context.Context, req ValidateAttribute } for _, value := range s.Elems { - tfValueRaw, err := value.ToTerraformValue(ctx) - + tfValue, err := value.ToTerraformValue(ctx) if err != nil { err := fmt.Errorf("error running ToTerraformValue on element value: %v", value) resp.Diagnostics.AddAttributeError( @@ -391,8 +389,6 @@ func (a Attribute) validateAttributes(ctx context.Context, req ValidateAttribute return } - tfValue := tftypes.NewValue(s.ElemType.TerraformType(ctx), tfValueRaw) - for nestedName, nestedAttr := range a.Attributes.GetAttributes() { nestedAttrReq := ValidateAttributeRequest{ AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(nestedName), @@ -584,8 +580,7 @@ func (a Attribute) modifyPlan(ctx context.Context, req ModifyAttributePlanReques } for _, value := range s.Elems { - tfValueRaw, err := value.ToTerraformValue(ctx) - + tfValue, err := value.ToTerraformValue(ctx) if err != nil { err := fmt.Errorf("error running ToTerraformValue on element value: %v", value) resp.Diagnostics.AddAttributeError( @@ -597,8 +592,6 @@ func (a Attribute) modifyPlan(ctx context.Context, req ModifyAttributePlanReques return } - tfValue := tftypes.NewValue(s.ElemType.TerraformType(ctx), tfValueRaw) - for name, attr := range a.Attributes.GetAttributes() { attrReq := ModifyAttributePlanRequest{ AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(name), diff --git a/tfsdk/attribute_plan_modification.go b/tfsdk/attribute_plan_modification.go index 8cc03fa07..a2df12444 100644 --- a/tfsdk/attribute_plan_modification.go +++ b/tfsdk/attribute_plan_modification.go @@ -147,7 +147,7 @@ func (r RequiresReplaceModifier) Modify(ctx context.Context, req ModifyAttribute ) return } - if configRaw == nil && attrSchema.Computed { + if configRaw.IsNull() && attrSchema.Computed { // if the config is null and the attribute is computed, this // could be an out of band change, don't require replace return @@ -287,7 +287,7 @@ func (r RequiresReplaceIfModifier) Modify(ctx context.Context, req ModifyAttribu ) return } - if configRaw == nil && attrSchema.Computed { + if configRaw.IsNull() && attrSchema.Computed { // if the config is null and the attribute is computed, this // could be an out of band change, don't require replace return @@ -354,7 +354,7 @@ func (r UseStateForUnknownModifier) Modify(ctx context.Context, req ModifyAttrib } // if we have no state value, there's nothing to preserve - if val == nil { + if val.IsNull() { return } @@ -369,7 +369,7 @@ func (r UseStateForUnknownModifier) Modify(ctx context.Context, req ModifyAttrib // if it's not planned to be the unknown value, stick with // the concrete plan - if val != tftypes.UnknownValue { + if val.IsKnown() { return } @@ -384,7 +384,7 @@ func (r UseStateForUnknownModifier) Modify(ctx context.Context, req ModifyAttrib // if the config is the unknown value, use the unknown value // otherwise, interpolation gets messed up - if val == tftypes.UnknownValue { + if !val.IsKnown() { return } diff --git a/tfsdk/attribute_plan_modification_test.go b/tfsdk/attribute_plan_modification_test.go index f7a65dd93..c174172ba 100644 --- a/tfsdk/attribute_plan_modification_test.go +++ b/tfsdk/attribute_plan_modification_test.go @@ -95,31 +95,30 @@ func TestUseStateForUnknownModifier(t *testing.T) { }, } - var configRaw, planRaw, stateRaw interface{} + configVal := tftypes.NewValue(tftypes.String, nil) + stateVal := tftypes.NewValue(tftypes.String, nil) + planVal := tftypes.NewValue(tftypes.String, nil) if tc.config != nil { val, err := tc.config.ToTerraformValue(context.Background()) if err != nil { t.Fatal(err) } - configRaw = val + configVal = val } if tc.state != nil { val, err := tc.state.ToTerraformValue(context.Background()) if err != nil { t.Fatal(err) } - stateRaw = val + stateVal = val } if tc.plan != nil { val, err := tc.plan.ToTerraformValue(context.Background()) if err != nil { t.Fatal(err) } - planRaw = val + planVal = val } - configVal := tftypes.NewValue(tftypes.String, configRaw) - stateVal := tftypes.NewValue(tftypes.String, stateRaw) - planVal := tftypes.NewValue(tftypes.String, planRaw) req := ModifyAttributePlanRequest{ AttributePath: tftypes.NewAttributePath(), diff --git a/tfsdk/block.go b/tfsdk/block.go index 0cba0b7ba..49982566e 100644 --- a/tfsdk/block.go +++ b/tfsdk/block.go @@ -269,8 +269,7 @@ func (b Block) modifyPlan(ctx context.Context, req ModifyAttributePlanRequest, r } for _, value := range s.Elems { - tfValueRaw, err := value.ToTerraformValue(ctx) - + tfValue, err := value.ToTerraformValue(ctx) if err != nil { err := fmt.Errorf("error running ToTerraformValue on element value: %v", value) resp.Diagnostics.AddAttributeError( @@ -282,8 +281,6 @@ func (b Block) modifyPlan(ctx context.Context, req ModifyAttributePlanRequest, r return } - tfValue := tftypes.NewValue(s.ElemType.TerraformType(ctx), tfValueRaw) - for name, attr := range b.Attributes { attrReq := ModifyAttributePlanRequest{ AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(name), @@ -482,8 +479,7 @@ func (b Block) validate(ctx context.Context, req ValidateAttributeRequest, resp } for _, value := range s.Elems { - tfValueRaw, err := value.ToTerraformValue(ctx) - + tfValue, err := value.ToTerraformValue(ctx) if err != nil { err := fmt.Errorf("error running ToTerraformValue on element value: %v", value) resp.Diagnostics.AddAttributeError( @@ -495,8 +491,6 @@ func (b Block) validate(ctx context.Context, req ValidateAttributeRequest, resp return } - tfValue := tftypes.NewValue(s.ElemType.TerraformType(ctx), tfValueRaw) - for name, attr := range b.Attributes { nestedAttrReq := ValidateAttributeRequest{ AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(name), @@ -549,7 +543,7 @@ func (b Block) validate(ctx context.Context, req ValidateAttributeRequest, resp return } - if tfValue != nil { + if !tfValue.IsNull() { resp.Diagnostics.AddAttributeWarning( req.AttributePath, "Block Deprecated", diff --git a/tfsdk/convert.go b/tfsdk/convert.go index bce68e12e..bfbcc60fd 100644 --- a/tfsdk/convert.go +++ b/tfsdk/convert.go @@ -6,27 +6,18 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) // ConvertValue creates a new attr.Value of the attr.Type `typ`, using the data // in `val`, which can be of any attr.Type so long as its TerraformType method // returns a tftypes.Type that `typ`'s ValueFromTerraform method can accept. func ConvertValue(ctx context.Context, val attr.Value, typ attr.Type) (attr.Value, diag.Diagnostics) { - tftype := typ.TerraformType(ctx) - tfval, err := val.ToTerraformValue(ctx) + newVal, err := val.ToTerraformValue(ctx) if err != nil { return nil, diag.Diagnostics{diag.NewErrorDiagnostic("Error converting value", fmt.Sprintf("An unexpected error was encountered converting a %T to a %s. This is always a problem with the provider. Please tell the provider developers that %T ran into the following error during ToTerraformValue: %s", val, typ, val, err), )} } - err = tftypes.ValidateValue(tftype, tfval) - if err != nil { - return nil, diag.Diagnostics{diag.NewErrorDiagnostic("Error converting value", - fmt.Sprintf("An unexpected error was encountered converting a %T to a %s. This is always a problem with the provider. Please tell the provider developers that %T is not compatible with %s.", val, typ, val, typ), - )} - } - newVal := tftypes.NewValue(tftype, tfval) res, err := typ.ValueFromTerraform(ctx, newVal) if err != nil { return nil, diag.Diagnostics{diag.NewErrorDiagnostic("Error converting value", diff --git a/tfsdk/convert_test.go b/tfsdk/convert_test.go index 174ec2668..5da6d5d81 100644 --- a/tfsdk/convert_test.go +++ b/tfsdk/convert_test.go @@ -43,7 +43,7 @@ func TestConvert(t *testing.T) { typ: types.NumberType, expectedDiags: diag.Diagnostics{diag.NewErrorDiagnostic( "Error converting value", - "An unexpected error was encountered converting a types.String to a types.NumberType. This is always a problem with the provider. Please tell the provider developers that types.String is not compatible with types.NumberType.", + "An unexpected error was encountered converting a types.String to a types.NumberType. This is always a problem with the provider. Please tell the provider developers that types.NumberType returned the following error when calling ValueFromTerraform: can't unmarshal tftypes.String into *big.Float, expected *big.Float", )}, }, } @@ -56,6 +56,9 @@ func TestConvert(t *testing.T) { got, diags := ConvertValue(context.Background(), tc.val, tc.typ) if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + for _, diag := range diags { + t.Logf("Diag summary: %q, Diag details: %q", diag.Summary(), diag.Detail()) + } t.Fatalf("Unexpected diff in diags (-wanted, +got): %s", diff) } diff --git a/tfsdk/plan.go b/tfsdk/plan.go index cfd00b3b1..d1e884120 100644 --- a/tfsdk/plan.go +++ b/tfsdk/plan.go @@ -122,7 +122,7 @@ func (p *Plan) Set(ctx context.Context, val interface{}) diag.Diagnostics { return diags } - newPlanVal, err := newPlanAttrValue.ToTerraformValue(ctx) + newPlan, err := newPlanAttrValue.ToTerraformValue(ctx) if err != nil { err = fmt.Errorf("error running ToTerraformValue on plan: %w", err) diags.AddError( @@ -132,8 +132,6 @@ func (p *Plan) Set(ctx context.Context, val interface{}) diag.Diagnostics { return diags } - newPlan := tftypes.NewValue(p.Schema.AttributeType().TerraformType(ctx), newPlanVal) - p.Raw = newPlan return diags } @@ -167,7 +165,7 @@ func (p *Plan) SetAttribute(ctx context.Context, path *tftypes.AttributePath, va return diags } - newTfVal, err := newVal.ToTerraformValue(ctx) + tfVal, err := newVal.ToTerraformValue(ctx) if err != nil { err = fmt.Errorf("error running ToTerraformValue on new plan value: %w", err) diags.AddAttributeError( @@ -178,8 +176,6 @@ func (p *Plan) SetAttribute(ctx context.Context, path *tftypes.AttributePath, va return diags } - tfVal := tftypes.NewValue(attrType.TerraformType(ctx), newTfVal) - if attrTypeWithValidate, ok := attrType.(attr.TypeWithValidate); ok { diags.Append(attrTypeWithValidate.Validate(ctx, tfVal, path)...) diff --git a/tfsdk/state.go b/tfsdk/state.go index 92be0294b..ba4acca08 100644 --- a/tfsdk/state.go +++ b/tfsdk/state.go @@ -131,7 +131,7 @@ func (s *State) Set(ctx context.Context, val interface{}) diag.Diagnostics { return diags } - newStateVal, err := newStateAttrValue.ToTerraformValue(ctx) + newState, err := newStateAttrValue.ToTerraformValue(ctx) if err != nil { err = fmt.Errorf("error running ToTerraformValue on state: %w", err) diags.AddError( @@ -141,8 +141,6 @@ func (s *State) Set(ctx context.Context, val interface{}) diag.Diagnostics { return diags } - newState := tftypes.NewValue(s.Schema.AttributeType().TerraformType(ctx), newStateVal) - s.Raw = newState return diags } @@ -176,7 +174,7 @@ func (s *State) SetAttribute(ctx context.Context, path *tftypes.AttributePath, v return diags } - newTfVal, err := newVal.ToTerraformValue(ctx) + tfVal, err := newVal.ToTerraformValue(ctx) if err != nil { err = fmt.Errorf("error running ToTerraformValue on new state value: %w", err) diags.AddAttributeError( @@ -187,8 +185,6 @@ func (s *State) SetAttribute(ctx context.Context, path *tftypes.AttributePath, v return diags } - tfVal := tftypes.NewValue(attrType.TerraformType(ctx), newTfVal) - if attrTypeWithValidate, ok := attrType.(attr.TypeWithValidate); ok { diags.Append(attrTypeWithValidate.Validate(ctx, tfVal, path)...) diff --git a/tfsdk/value_as.go b/tfsdk/value_as.go index 0f44bd27f..2b60d0ac1 100644 --- a/tfsdk/value_as.go +++ b/tfsdk/value_as.go @@ -7,7 +7,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/reflect" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) // ValueAs populates the Go value passed as `target` with @@ -23,12 +22,5 @@ func ValueAs(ctx context.Context, val attr.Value, target interface{}) diag.Diagn return diag.Diagnostics{diag.NewErrorDiagnostic("Error converting value", fmt.Sprintf("An unexpected error was encountered converting a %T to its equivalent Terraform representation. This is always a bug in the provider.\n\nError: %s", val, err))} } - typ := val.Type(ctx).TerraformType(ctx) - err = tftypes.ValidateValue(typ, raw) - if err != nil { - return diag.Diagnostics{diag.NewErrorDiagnostic("Invalid value conversion", - fmt.Sprintf("An unexpected error was encountered converting a %T to its equivalent Terraform representation. This is always a bug in the provider.\n\nError: %s", val, err))} - } - v := tftypes.NewValue(typ, raw) - return reflect.Into(ctx, val.Type(ctx), v, target, reflect.Options{}) + return reflect.Into(ctx, val.Type(ctx), raw, target, reflect.Options{}) } diff --git a/types/bool.go b/types/bool.go index bb8254593..8997dd83d 100644 --- a/types/bool.go +++ b/types/bool.go @@ -47,17 +47,18 @@ func (b Bool) Type(_ context.Context) attr.Type { return BoolType } -// ToTerraformValue returns the data contained in the *Bool as a bool. If -// Unknown is true, it returns a tftypes.UnknownValue. If Null is true, it -// returns nil. -func (b Bool) ToTerraformValue(_ context.Context) (interface{}, error) { +// ToTerraformValue returns the data contained in the *Bool as a tftypes.Value. +func (b Bool) ToTerraformValue(_ context.Context) (tftypes.Value, error) { if b.Null { - return nil, nil + return tftypes.NewValue(tftypes.Bool, nil), nil } if b.Unknown { - return tftypes.UnknownValue, nil + return tftypes.NewValue(tftypes.Bool, tftypes.UnknownValue), nil } - return b.Value, nil + if err := tftypes.ValidateValue(tftypes.Bool, b.Value); err != nil { + return tftypes.NewValue(tftypes.Bool, tftypes.UnknownValue), err + } + return tftypes.NewValue(tftypes.Bool, b.Value), nil } // Equal returns true if `other` is a *Bool and has the same value as `b`. diff --git a/types/bool_test.go b/types/bool_test.go index 186bf4737..f837b004d 100644 --- a/types/bool_test.go +++ b/types/bool_test.go @@ -89,19 +89,19 @@ func TestBoolToTerraformValue(t *testing.T) { tests := map[string]testCase{ "true": { input: Bool{Value: true}, - expectation: true, + expectation: tftypes.NewValue(tftypes.Bool, true), }, "false": { input: Bool{Value: false}, - expectation: false, + expectation: tftypes.NewValue(tftypes.Bool, false), }, "unknown": { input: Bool{Unknown: true}, - expectation: tftypes.UnknownValue, + expectation: tftypes.NewValue(tftypes.Bool, tftypes.UnknownValue), }, "null": { input: Bool{Null: true}, - expectation: nil, + expectation: tftypes.NewValue(tftypes.Bool, nil), }, } for name, test := range tests { diff --git a/types/float64.go b/types/float64.go index 6eb2b6af5..24b22354e 100644 --- a/types/float64.go +++ b/types/float64.go @@ -114,19 +114,22 @@ func (f Float64) Equal(other attr.Value) bool { return f.Value == o.Value } -// ToTerraformValue returns the data contained in the Float64 as a float64. -// If Unknown is true, it returns a tftypes.UnknownValue. If Null is true, it -// returns nil. -func (f Float64) ToTerraformValue(ctx context.Context) (interface{}, error) { +// ToTerraformValue returns the data contained in the Float64 as a +// tftypes.Value. +func (f Float64) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { if f.Null { - return nil, nil + return tftypes.NewValue(tftypes.Number, nil), nil } if f.Unknown { - return tftypes.UnknownValue, nil + return tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), nil } - return big.NewFloat(f.Value), nil + bf := big.NewFloat(f.Value) + if err := tftypes.ValidateValue(tftypes.Number, bf); err != nil { + return tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), err + } + return tftypes.NewValue(tftypes.Number, bf), nil } // Type returns a NumberType. diff --git a/types/float64_test.go b/types/float64_test.go index 832fc10e1..b69a48a55 100644 --- a/types/float64_test.go +++ b/types/float64_test.go @@ -90,19 +90,19 @@ func TestFloat64ToTerraformValue(t *testing.T) { tests := map[string]testCase{ "value-int": { input: Float64{Value: 123}, - expectation: big.NewFloat(123.0), + expectation: tftypes.NewValue(tftypes.Number, big.NewFloat(123.0)), }, "value-float": { input: Float64{Value: 123.456}, - expectation: big.NewFloat(123.456), + expectation: tftypes.NewValue(tftypes.Number, big.NewFloat(123.456)), }, "unknown": { input: Float64{Unknown: true}, - expectation: tftypes.UnknownValue, + expectation: tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), }, "null": { input: Float64{Null: true}, - expectation: nil, + expectation: tftypes.NewValue(tftypes.Number, nil), }, } for name, test := range tests { diff --git a/types/int64.go b/types/int64.go index 04e44fe56..6cb792ce3 100644 --- a/types/int64.go +++ b/types/int64.go @@ -127,19 +127,21 @@ func (i Int64) Equal(other attr.Value) bool { return i.Value == o.Value } -// ToTerraformValue returns the data contained in the Int64 as a int64. -// If Unknown is true, it returns a tftypes.UnknownValue. If Null is true, it -// returns nil. -func (i Int64) ToTerraformValue(ctx context.Context) (interface{}, error) { +// ToTerraformValue returns the data contained in the Int64 as a tftypes.Value. +func (i Int64) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { if i.Null { - return nil, nil + return tftypes.NewValue(tftypes.Number, nil), nil } if i.Unknown { - return tftypes.UnknownValue, nil + return tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), nil } - return new(big.Float).SetInt64(i.Value), nil + bf := new(big.Float).SetInt64(i.Value) + if err := tftypes.ValidateValue(tftypes.Number, bf); err != nil { + return tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), err + } + return tftypes.NewValue(tftypes.Number, bf), nil } // Type returns a NumberType. diff --git a/types/int64_test.go b/types/int64_test.go index e7dd68f31..f8506223b 100644 --- a/types/int64_test.go +++ b/types/int64_test.go @@ -86,15 +86,15 @@ func TestInt64ToTerraformValue(t *testing.T) { tests := map[string]testCase{ "value": { input: Int64{Value: 123}, - expectation: big.NewFloat(123), + expectation: tftypes.NewValue(tftypes.Number, big.NewFloat(123)), }, "unknown": { input: Int64{Unknown: true}, - expectation: tftypes.UnknownValue, + expectation: tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), }, "null": { input: Int64{Null: true}, - expectation: nil, + expectation: tftypes.NewValue(tftypes.Number, nil), }, } for name, test := range tests { diff --git a/types/list.go b/types/list.go index 64530e3ad..2a24cc12a 100644 --- a/types/list.go +++ b/types/list.go @@ -148,9 +148,7 @@ func (l List) ElementsAs(ctx context.Context, target interface{}, allowUnhandled ), } } - return reflect.Into(ctx, ListType{ElemType: l.ElemType}, tftypes.NewValue(tftypes.List{ - ElementType: l.ElemType.TerraformType(ctx), - }, values), target, reflect.Options{ + return reflect.Into(ctx, ListType{ElemType: l.ElemType}, values, target, reflect.Options{ UnhandledNullAsEmpty: allowUnhandled, UnhandledUnknownAsEmpty: allowUnhandled, }) @@ -162,27 +160,27 @@ func (l List) Type(ctx context.Context) attr.Type { } // 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) { +// a tftypes.Value. +func (l List) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + listType := tftypes.List{ElementType: l.ElemType.TerraformType(ctx)} if l.Unknown { - return tftypes.UnknownValue, nil + return tftypes.NewValue(listType, tftypes.UnknownValue), nil } if l.Null { - return nil, nil + return tftypes.NewValue(listType, 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 + return tftypes.NewValue(listType, tftypes.UnknownValue), err } - err = tftypes.ValidateValue(l.ElemType.TerraformType(ctx), val) - if err != nil { - return nil, fmt.Errorf("error validating terraform type: %w", err) - } - vals = append(vals, tftypes.NewValue(l.ElemType.TerraformType(ctx), val)) + vals = append(vals, val) + } + if err := tftypes.ValidateValue(listType, vals); err != nil { + return tftypes.NewValue(listType, tftypes.UnknownValue), err } - return vals, nil + return tftypes.NewValue(listType, vals), nil } // Equal must return true if the AttributeValue is considered diff --git a/types/list_test.go b/types/list_test.go index d62c6f7dd..730c0ff4b 100644 --- a/types/list_test.go +++ b/types/list_test.go @@ -316,18 +316,18 @@ func TestListToTerraformValue(t *testing.T) { String{Value: "world"}, }, }, - expectation: []tftypes.Value{ + expectation: tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ tftypes.NewValue(tftypes.String, "hello"), tftypes.NewValue(tftypes.String, "world"), - }, + }), }, "unknown": { - input: List{Unknown: true}, - expectation: tftypes.UnknownValue, + input: List{ElemType: StringType, Unknown: true}, + expectation: tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, tftypes.UnknownValue), }, "null": { - input: List{Null: true}, - expectation: nil, + input: List{ElemType: StringType, Null: true}, + expectation: tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, nil), }, "partial-unknown": { input: List{ @@ -337,10 +337,10 @@ func TestListToTerraformValue(t *testing.T) { String{Value: "hello, world"}, }, }, - expectation: []tftypes.Value{ + expectation: tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ tftypes.NewValue(tftypes.String, tftypes.UnknownValue), tftypes.NewValue(tftypes.String, "hello, world"), - }, + }), }, "partial-null": { input: List{ @@ -350,10 +350,10 @@ func TestListToTerraformValue(t *testing.T) { String{Value: "hello, world"}, }, }, - expectation: []tftypes.Value{ + expectation: tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ tftypes.NewValue(tftypes.String, nil), tftypes.NewValue(tftypes.String, "hello, world"), - }, + }), }, } for name, test := range tests { diff --git a/types/map.go b/types/map.go index 6a86417bb..fffd56e24 100644 --- a/types/map.go +++ b/types/map.go @@ -137,33 +137,17 @@ type Map struct { func (m Map) ElementsAs(ctx context.Context, target interface{}, allowUnhandled bool) diag.Diagnostics { // we need a tftypes.Value for this Map to be able to use it with our // reflection code - values := make(map[string]tftypes.Value, len(m.Elems)) - for key, elem := range m.Elems { - val, err := elem.ToTerraformValue(ctx) - if err != nil { - err := fmt.Errorf("error getting Terraform value for element %q: %w", key, err) - return diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Map Element Conversion Error", - "An unexpected error was encountered trying to convert map elements. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), - ), - } - } - err = tftypes.ValidateValue(m.ElemType.TerraformType(ctx), val) - if err != nil { - err := fmt.Errorf("error using created Terraform value for element %q: %w", key, err) - return diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Map Element Conversion Error", - "An unexpected error was encountered trying to convert map elements. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), - ), - } + val, err := m.ToTerraformValue(ctx) + if err != nil { + err := fmt.Errorf("error getting Terraform value for map: %w", err) + return diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Map Conversion Error", + "An unexpected error was encountered trying to convert the map into an equivalent Terraform value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ), } - values[key] = tftypes.NewValue(m.ElemType.TerraformType(ctx), val) } - return reflect.Into(ctx, MapType{ElemType: m.ElemType}, tftypes.NewValue(tftypes.Map{ - ElementType: m.ElemType.TerraformType(ctx), - }, values), target, reflect.Options{ + return reflect.Into(ctx, MapType{ElemType: m.ElemType}, val, target, reflect.Options{ UnhandledNullAsEmpty: allowUnhandled, UnhandledUnknownAsEmpty: allowUnhandled, }) @@ -174,28 +158,28 @@ func (m Map) Type(ctx context.Context) attr.Type { return MapType{ElemType: m.ElemType} } -// ToTerraformValue returns the data contained in the AttributeValue as a Go -// type that tftypes.NewValue will accept. -func (m Map) ToTerraformValue(ctx context.Context) (interface{}, error) { +// ToTerraformValue returns the data contained in the AttributeValue as a +// tftypes.Value. +func (m Map) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + mapType := tftypes.Map{ElementType: m.ElemType.TerraformType(ctx)} if m.Unknown { - return tftypes.UnknownValue, nil + return tftypes.NewValue(mapType, tftypes.UnknownValue), nil } if m.Null { - return nil, nil + return tftypes.NewValue(mapType, nil), nil } vals := make(map[string]tftypes.Value, len(m.Elems)) for key, elem := range m.Elems { val, err := elem.ToTerraformValue(ctx) if err != nil { - return nil, err - } - err = tftypes.ValidateValue(m.ElemType.TerraformType(ctx), val) - if err != nil { - return nil, err + return tftypes.NewValue(mapType, tftypes.UnknownValue), err } - vals[key] = tftypes.NewValue(m.ElemType.TerraformType(ctx), val) + vals[key] = val + } + if err := tftypes.ValidateValue(mapType, vals); err != nil { + return tftypes.NewValue(mapType, tftypes.UnknownValue), err } - return vals, nil + return tftypes.NewValue(mapType, vals), nil } // Equal must return true if the AttributeValue is considered semantically diff --git a/types/map_test.go b/types/map_test.go index 55ae3e3a7..780d66a67 100644 --- a/types/map_test.go +++ b/types/map_test.go @@ -287,7 +287,7 @@ func TestMapToTerraformValue(t *testing.T) { type testCase struct { input Map - expectation interface{} + expectation tftypes.Value } tests := map[string]testCase{ "value": { @@ -298,18 +298,18 @@ func TestMapToTerraformValue(t *testing.T) { "w": String{Value: "world"}, }, }, - expectation: map[string]tftypes.Value{ + expectation: tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, map[string]tftypes.Value{ "h": tftypes.NewValue(tftypes.String, "hello"), "w": tftypes.NewValue(tftypes.String, "world"), - }, + }), }, "unknown": { - input: Map{Unknown: true}, - expectation: tftypes.UnknownValue, + input: Map{ElemType: StringType, Unknown: true}, + expectation: tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, tftypes.UnknownValue), }, "null": { - input: Map{Null: true}, - expectation: nil, + input: Map{ElemType: StringType, Null: true}, + expectation: tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, nil), }, "partial-unknown": { input: Map{ @@ -319,10 +319,10 @@ func TestMapToTerraformValue(t *testing.T) { "hw": String{Value: "hello, world"}, }, }, - expectation: map[string]tftypes.Value{ + expectation: tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, map[string]tftypes.Value{ "unk": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), "hw": tftypes.NewValue(tftypes.String, "hello, world"), - }, + }), }, "partial-null": { input: Map{ @@ -332,10 +332,10 @@ func TestMapToTerraformValue(t *testing.T) { "hw": String{Value: "hello, world"}, }, }, - expectation: map[string]tftypes.Value{ + expectation: tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, map[string]tftypes.Value{ "n": tftypes.NewValue(tftypes.String, nil), "hw": tftypes.NewValue(tftypes.String, "hello, world"), - }, + }), }, } for name, test := range tests { diff --git a/types/number.go b/types/number.go index c47e1d24e..1c9bf55ae 100644 --- a/types/number.go +++ b/types/number.go @@ -45,20 +45,22 @@ func (n Number) Type(_ context.Context) attr.Type { return NumberType } -// ToTerraformValue returns the data contained in the *Number as a *big.Float. -// If Unknown is true, it returns a tftypes.UnknownValue. If Null is true, it -// returns nil. -func (n Number) ToTerraformValue(_ context.Context) (interface{}, error) { +// ToTerraformValue returns the data contained in the *Number as a +// tftypes.Value. +func (n Number) ToTerraformValue(_ context.Context) (tftypes.Value, error) { if n.Null { - return nil, nil + return tftypes.NewValue(tftypes.Number, nil), nil } if n.Unknown { - return tftypes.UnknownValue, nil + return tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), nil } if n.Value == nil { - return nil, nil + return tftypes.NewValue(tftypes.Number, nil), nil } - return n.Value, nil + if err := tftypes.ValidateValue(tftypes.Number, n.Value); err != nil { + return tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), err + } + return tftypes.NewValue(tftypes.Number, n.Value), nil } // Equal returns true if `other` is a *Number and has the same value as `n`. diff --git a/types/number_test.go b/types/number_test.go index 5dac7ed4e..a9f4fa7cf 100644 --- a/types/number_test.go +++ b/types/number_test.go @@ -85,24 +85,24 @@ func TestNumberToTerraformValue(t *testing.T) { type testCase struct { input Number - expectation interface{} + expectation tftypes.Value } tests := map[string]testCase{ "value": { input: Number{Value: big.NewFloat(123)}, - expectation: big.NewFloat(123), + expectation: tftypes.NewValue(tftypes.Number, big.NewFloat(123)), }, "value-nil": { input: Number{Value: nil}, - expectation: nil, + expectation: tftypes.NewValue(tftypes.Number, nil), }, "unknown": { input: Number{Unknown: true}, - expectation: tftypes.UnknownValue, + expectation: tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), }, "null": { input: Number{Null: true}, - expectation: nil, + expectation: tftypes.NewValue(tftypes.Number, nil), }, } for name, test := range tests { diff --git a/types/object.go b/types/object.go index bcbf1d425..3173f65b6 100644 --- a/types/object.go +++ b/types/object.go @@ -180,7 +180,6 @@ func (o Object) As(ctx context.Context, target interface{}, opts ObjectAsOptions // we need a tftypes.Value for this Object to be able to use it with // our reflection code obj := ObjectType{AttrTypes: o.AttrTypes} - typ := obj.TerraformType(ctx) val, err := o.ToTerraformValue(ctx) if err != nil { return diag.Diagnostics{ @@ -190,16 +189,7 @@ func (o Object) As(ctx context.Context, target interface{}, opts ObjectAsOptions ), } } - err = tftypes.ValidateValue(typ, val) - if err != nil { - return diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Object Conversion Error", - "An unexpected error was encountered trying to convert object. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), - ), - } - } - return reflect.Into(ctx, obj, tftypes.NewValue(typ, val), target, reflect.Options{ + return reflect.Into(ctx, obj, val, target, reflect.Options{ UnhandledNullAsEmpty: opts.UnhandledNullAsEmpty, UnhandledUnknownAsEmpty: opts.UnhandledUnknownAsEmpty, }) @@ -211,28 +201,32 @@ func (o Object) Type(_ context.Context) attr.Type { } // ToTerraformValue returns the data contained in the AttributeValue as -// a Go type that tftypes.NewValue will accept. -func (o Object) ToTerraformValue(ctx context.Context) (interface{}, error) { +// a tftypes.Value. +func (o Object) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := map[string]tftypes.Type{} + for attr, typ := range o.AttrTypes { + attrTypes[attr] = typ.TerraformType(ctx) + } + objectType := tftypes.Object{AttributeTypes: attrTypes} if o.Unknown { - return tftypes.UnknownValue, nil + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil } if o.Null { - return nil, nil + return tftypes.NewValue(objectType, nil), nil } vals := map[string]tftypes.Value{} for k, v := range o.Attrs { val, err := v.ToTerraformValue(ctx) if err != nil { - return nil, err + return tftypes.NewValue(objectType, tftypes.UnknownValue), err } - err = tftypes.ValidateValue(o.AttrTypes[k].TerraformType(ctx), val) - if err != nil { - return nil, err - } - vals[k] = tftypes.NewValue(o.AttrTypes[k].TerraformType(ctx), val) + vals[k] = val + } + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err } - return vals, nil + return tftypes.NewValue(objectType, vals), nil } // Equal must return true if the AttributeValue is considered diff --git a/types/object_test.go b/types/object_test.go index 6c156e064..0c105bc20 100644 --- a/types/object_test.go +++ b/types/object_test.go @@ -626,7 +626,7 @@ func TestObjectToTerraformValue(t *testing.T) { t.Parallel() type testCase struct { receiver Object - expected interface{} + expected tftypes.Value expectedErr string } tests := map[string]testCase{ @@ -672,7 +672,16 @@ func TestObjectToTerraformValue(t *testing.T) { }, }, }, - expected: map[string]tftypes.Value{ + expected: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "a": tftypes.List{ElementType: tftypes.String}, + "b": tftypes.String, + "c": tftypes.Bool, + "d": tftypes.Number, + "e": tftypes.Object{AttributeTypes: map[string]tftypes.Type{"name": tftypes.String}}, + "f": tftypes.Set{ElementType: tftypes.String}, + }, + }, map[string]tftypes.Value{ "a": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ tftypes.NewValue(tftypes.String, "hello"), tftypes.NewValue(tftypes.String, "world"), @@ -691,7 +700,7 @@ func TestObjectToTerraformValue(t *testing.T) { tftypes.NewValue(tftypes.String, "hello"), tftypes.NewValue(tftypes.String, "world"), }), - }, + }), }, "unknown": { receiver: Object{ @@ -709,7 +718,20 @@ func TestObjectToTerraformValue(t *testing.T) { }, Unknown: true, }, - expected: tftypes.UnknownValue, + expected: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "a": tftypes.List{ElementType: tftypes.String}, + "b": tftypes.String, + "c": tftypes.Bool, + "d": tftypes.Number, + "e": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "name": tftypes.String, + }, + }, + "f": tftypes.Set{ElementType: tftypes.String}, + }, + }, tftypes.UnknownValue), }, "null": { receiver: Object{ @@ -727,7 +749,20 @@ func TestObjectToTerraformValue(t *testing.T) { }, Null: true, }, - expected: nil, + expected: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "a": tftypes.List{ElementType: tftypes.String}, + "b": tftypes.String, + "c": tftypes.Bool, + "d": tftypes.Number, + "e": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "name": tftypes.String, + }, + }, + "f": tftypes.Set{ElementType: tftypes.String}, + }, + }, nil), }, "partial-unknown": { receiver: Object{ @@ -771,7 +806,20 @@ func TestObjectToTerraformValue(t *testing.T) { }, }, }, - expected: map[string]tftypes.Value{ + expected: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "a": tftypes.List{ElementType: tftypes.String}, + "b": tftypes.String, + "c": tftypes.Bool, + "d": tftypes.Number, + "e": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "name": tftypes.String, + }, + }, + "f": tftypes.Set{ElementType: tftypes.String}, + }, + }, map[string]tftypes.Value{ "a": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ tftypes.NewValue(tftypes.String, "hello"), tftypes.NewValue(tftypes.String, "world"), @@ -790,7 +838,7 @@ func TestObjectToTerraformValue(t *testing.T) { tftypes.NewValue(tftypes.String, "hello"), tftypes.NewValue(tftypes.String, "world"), }), - }, + }), }, "partial-null": { receiver: Object{ @@ -834,7 +882,20 @@ func TestObjectToTerraformValue(t *testing.T) { }, }, }, - expected: map[string]tftypes.Value{ + expected: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "a": tftypes.List{ElementType: tftypes.String}, + "b": tftypes.String, + "c": tftypes.Bool, + "d": tftypes.Number, + "e": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "name": tftypes.String, + }, + }, + "f": tftypes.Set{ElementType: tftypes.String}, + }, + }, map[string]tftypes.Value{ "a": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ tftypes.NewValue(tftypes.String, "hello"), tftypes.NewValue(tftypes.String, "world"), @@ -853,7 +914,7 @@ func TestObjectToTerraformValue(t *testing.T) { tftypes.NewValue(tftypes.String, "hello"), tftypes.NewValue(tftypes.String, "world"), }), - }, + }), }, "deep-partial-unknown": { receiver: Object{ @@ -888,7 +949,7 @@ func TestObjectToTerraformValue(t *testing.T) { "name": String{Unknown: true}, }, }, - "f": List{ + "f": Set{ ElemType: StringType, Elems: []attr.Value{ String{Value: "hello"}, @@ -897,7 +958,20 @@ func TestObjectToTerraformValue(t *testing.T) { }, }, }, - expected: map[string]tftypes.Value{ + expected: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "a": tftypes.List{ElementType: tftypes.String}, + "b": tftypes.String, + "c": tftypes.Bool, + "d": tftypes.Number, + "e": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "name": tftypes.String, + }, + }, + "f": tftypes.Set{ElementType: tftypes.String}, + }, + }, map[string]tftypes.Value{ "a": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ tftypes.NewValue(tftypes.String, "hello"), tftypes.NewValue(tftypes.String, "world"), @@ -916,7 +990,7 @@ func TestObjectToTerraformValue(t *testing.T) { tftypes.NewValue(tftypes.String, "hello"), tftypes.NewValue(tftypes.String, "world"), }), - }, + }), }, "deep-partial-null": { receiver: Object{ @@ -960,7 +1034,20 @@ func TestObjectToTerraformValue(t *testing.T) { }, }, }, - expected: map[string]tftypes.Value{ + expected: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "a": tftypes.List{ElementType: tftypes.String}, + "b": tftypes.String, + "c": tftypes.Bool, + "d": tftypes.Number, + "e": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "name": tftypes.String, + }, + }, + "f": tftypes.Set{ElementType: tftypes.String}, + }, + }, map[string]tftypes.Value{ "a": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ tftypes.NewValue(tftypes.String, "hello"), tftypes.NewValue(tftypes.String, "world"), @@ -979,7 +1066,7 @@ func TestObjectToTerraformValue(t *testing.T) { tftypes.NewValue(tftypes.String, "hello"), tftypes.NewValue(tftypes.String, "world"), }), - }, + }), }, } diff --git a/types/set.go b/types/set.go index 8edda2280..19b139210 100644 --- a/types/set.go +++ b/types/set.go @@ -196,7 +196,7 @@ type Set struct { func (s Set) ElementsAs(ctx context.Context, target interface{}, allowUnhandled bool) diag.Diagnostics { // we need a tftypes.Value for this Set to be able to use it with our // reflection code - values, err := s.ToTerraformValue(ctx) + val, err := s.ToTerraformValue(ctx) if err != nil { return diag.Diagnostics{ diag.NewErrorDiagnostic( @@ -205,9 +205,7 @@ func (s Set) ElementsAs(ctx context.Context, target interface{}, allowUnhandled ), } } - return reflect.Into(ctx, SetType{ElemType: s.ElemType}, tftypes.NewValue(tftypes.Set{ - ElementType: s.ElemType.TerraformType(ctx), - }, values), target, reflect.Options{ + return reflect.Into(ctx, s.Type(ctx), val, target, reflect.Options{ UnhandledNullAsEmpty: allowUnhandled, UnhandledUnknownAsEmpty: allowUnhandled, }) @@ -219,27 +217,27 @@ func (s Set) Type(ctx context.Context) attr.Type { } // ToTerraformValue returns the data contained in the AttributeValue as -// a Go type that tftypes.NewValue will accept. -func (s Set) ToTerraformValue(ctx context.Context) (interface{}, error) { +// a tftypes.Value. +func (s Set) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + setType := tftypes.Set{ElementType: s.ElemType.TerraformType(ctx)} if s.Unknown { - return tftypes.UnknownValue, nil + return tftypes.NewValue(setType, tftypes.UnknownValue), nil } if s.Null { - return nil, nil + return tftypes.NewValue(setType, nil), nil } vals := make([]tftypes.Value, 0, len(s.Elems)) for _, elem := range s.Elems { val, err := elem.ToTerraformValue(ctx) if err != nil { - return nil, err - } - err = tftypes.ValidateValue(s.ElemType.TerraformType(ctx), val) - if err != nil { - return nil, fmt.Errorf("error validating terraform type: %w", err) + return tftypes.NewValue(setType, tftypes.UnknownValue), err } - vals = append(vals, tftypes.NewValue(s.ElemType.TerraformType(ctx), val)) + vals = append(vals, val) + } + if err := tftypes.ValidateValue(setType, vals); err != nil { + return tftypes.NewValue(setType, tftypes.UnknownValue), err } - return vals, nil + return tftypes.NewValue(setType, vals), nil } // Equal must return true if the AttributeValue is considered diff --git a/types/set_test.go b/types/set_test.go index 721c0dcd0..6868b2938 100644 --- a/types/set_test.go +++ b/types/set_test.go @@ -550,7 +550,7 @@ func TestSetToTerraformValue(t *testing.T) { type testCase struct { input Set - expectation interface{} + expectation tftypes.Value } tests := map[string]testCase{ "value": { @@ -561,10 +561,10 @@ func TestSetToTerraformValue(t *testing.T) { String{Value: "world"}, }, }, - expectation: []tftypes.Value{ + expectation: tftypes.NewValue(tftypes.Set{ElementType: tftypes.String}, []tftypes.Value{ tftypes.NewValue(tftypes.String, "hello"), tftypes.NewValue(tftypes.String, "world"), - }, + }), }, "value-duplicates": { input: Set{ @@ -576,18 +576,18 @@ func TestSetToTerraformValue(t *testing.T) { }, // Duplicate validation does not occur during this method. // This is okay, as tftypes allows duplicates. - expectation: []tftypes.Value{ + expectation: tftypes.NewValue(tftypes.Set{ElementType: tftypes.String}, []tftypes.Value{ tftypes.NewValue(tftypes.String, "hello"), tftypes.NewValue(tftypes.String, "hello"), - }, + }), }, "unknown": { - input: Set{Unknown: true}, - expectation: tftypes.UnknownValue, + input: Set{ElemType: StringType, Unknown: true}, + expectation: tftypes.NewValue(tftypes.Set{ElementType: tftypes.String}, tftypes.UnknownValue), }, "null": { - input: Set{Null: true}, - expectation: nil, + input: Set{ElemType: StringType, Null: true}, + expectation: tftypes.NewValue(tftypes.Set{ElementType: tftypes.String}, nil), }, "partial-unknown": { input: Set{ @@ -597,10 +597,10 @@ func TestSetToTerraformValue(t *testing.T) { String{Value: "hello, world"}, }, }, - expectation: []tftypes.Value{ + expectation: tftypes.NewValue(tftypes.Set{ElementType: tftypes.String}, []tftypes.Value{ tftypes.NewValue(tftypes.String, tftypes.UnknownValue), tftypes.NewValue(tftypes.String, "hello, world"), - }, + }), }, "partial-null": { input: Set{ @@ -610,10 +610,10 @@ func TestSetToTerraformValue(t *testing.T) { String{Value: "hello, world"}, }, }, - expectation: []tftypes.Value{ + expectation: tftypes.NewValue(tftypes.Set{ElementType: tftypes.String}, []tftypes.Value{ tftypes.NewValue(tftypes.String, nil), tftypes.NewValue(tftypes.String, "hello, world"), - }, + }), }, } for name, test := range tests { diff --git a/types/string.go b/types/string.go index e17b15fd0..df13d4632 100644 --- a/types/string.go +++ b/types/string.go @@ -43,17 +43,19 @@ func (s String) Type(_ context.Context) attr.Type { return StringType } -// ToTerraformValue returns the data contained in the *String as a string. If -// Unknown is true, it returns a tftypes.UnknownValue. If Null is true, it -// returns nil. -func (s String) ToTerraformValue(_ context.Context) (interface{}, error) { +// ToTerraformValue returns the data contained in the *String as a +// tftypes.Value. +func (s String) ToTerraformValue(_ context.Context) (tftypes.Value, error) { if s.Null { - return nil, nil + return tftypes.NewValue(tftypes.String, nil), nil } if s.Unknown { - return tftypes.UnknownValue, nil + return tftypes.NewValue(tftypes.String, tftypes.UnknownValue), nil } - return s.Value, nil + if err := tftypes.ValidateValue(tftypes.String, s.Value); err != nil { + return tftypes.NewValue(tftypes.String, tftypes.UnknownValue), err + } + return tftypes.NewValue(tftypes.String, s.Value), nil } // Equal returns true if `other` is a *String and has the same value as `s`. diff --git a/types/string_test.go b/types/string_test.go index 7b298b8ce..dcc899403 100644 --- a/types/string_test.go +++ b/types/string_test.go @@ -86,15 +86,15 @@ func TestStringToTerraformValue(t *testing.T) { tests := map[string]testCase{ "value": { input: String{Value: "hello"}, - expectation: "hello", + expectation: tftypes.NewValue(tftypes.String, "hello"), }, "unknown": { input: String{Unknown: true}, - expectation: tftypes.UnknownValue, + expectation: tftypes.NewValue(tftypes.String, tftypes.UnknownValue), }, "null": { input: String{Null: true}, - expectation: nil, + expectation: tftypes.NewValue(tftypes.String, nil), }, } for name, test := range tests {