Skip to content

Commit

Permalink
types/basetypes: Fixed Float64Type validation to handle valid, string…
Browse files Browse the repository at this point in the history
…ified numbers from Terraform (#614)

Reference: #613

Numbers in Terraform's type system can be represented with up to 512 bits of base 10 precision. When encoding and decoding across the protocol, the type system or MessagePack may switch the representation to be stringified, which is valid and expected.

The prior `Float64Type` type `Validate` method logic was only checking the `big.Accuracy` return from the value, however in certain cases the stringified number was saved with a `big.Below` or `big.Above` accuracy, even though the value itself could be successfully converted into a `float64` type. This change updates the validation logic to follow the `(*big.Float).Float64()` method documentation to check the returned value, along with the `big.Accuracy`.
  • Loading branch information
bflad committed Jan 10, 2023
1 parent 6139030 commit b90f22a
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .changelog/614.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-notes:bug
types/basetypes: Fixed `Float64Type` type `Validate` method to handle valid, stringified numbers from Terraform
```
107 changes: 107 additions & 0 deletions types/basetypes/float64_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,122 @@ package basetypes

import (
"context"
"fmt"
"math"
"math/big"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

// testMustParseFloat parses a string into a *big.Float similar to cty and
// tftypes logic or panics on any error.
//
// Reference: https://github.com/hashicorp/go-cty/blob/85980079f637862fa8e43ddc82dd74315e2f4c85/cty/value_init.go#L49
// Reference: https://github.com/hashicorp/terraform-plugin-go/blob/c593d2e0da8d2258b2a22af867c39842a0cb89f7/tftypes/value_msgpack.go#L108
func testMustParseFloat(s string) *big.Float {
f, _, err := big.ParseFloat(s, 10, 512, big.ToNearestEven)

if err != nil {
panic(err)
}

return f
}

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

testCases := map[string]struct {
in tftypes.Value
expected diag.Diagnostics
}{
"zero-float": {
in: tftypes.NewValue(tftypes.Number, big.NewFloat(0.0)),
expected: nil,
},
"negative-integer": {
in: tftypes.NewValue(tftypes.Number, big.NewFloat(-123)),
expected: nil,
},
"positive-integer": {
in: tftypes.NewValue(tftypes.Number, big.NewFloat(123)),
expected: nil,
},
"positive-float": {
in: tftypes.NewValue(tftypes.Number, big.NewFloat(123.45)),
expected: nil,
},
"negative-float": {
in: tftypes.NewValue(tftypes.Number, big.NewFloat(123.45)),
expected: nil,
},
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/613
"zero-string-float": {
in: tftypes.NewValue(tftypes.Number, testMustParseFloat("0.0")),
expected: nil,
},
"positive-string-float": {
in: tftypes.NewValue(tftypes.Number, testMustParseFloat("123.2")),
expected: nil,
},
"negative-string-float": {
in: tftypes.NewValue(tftypes.Number, testMustParseFloat("-123.2")),
expected: nil,
},
// Reference: https://pkg.go.dev/math/big#Float.Float64
// Reference: https://pkg.go.dev/math#pkg-constants
"SmallestNonzeroFloat64": {
in: tftypes.NewValue(tftypes.Number, big.NewFloat(math.SmallestNonzeroFloat64)),
expected: nil,
},
"SmallestNonzeroFloat64-below": {
in: tftypes.NewValue(tftypes.Number, testMustParseFloat("4.9406564584124654417656879286822137236505980e-325")),
expected: diag.Diagnostics{
diag.NewAttributeErrorDiagnostic(
path.Root("test"),
"Float64 Type Validation Error",
fmt.Sprintf("Value %s cannot be represented as a 64-bit floating point.", testMustParseFloat("4.9406564584124654417656879286822137236505980e-325")),
),
},
},
// Reference: https://pkg.go.dev/math/big#Float.Float64
// Reference: https://pkg.go.dev/math#pkg-constants
"MaxFloat64": {
in: tftypes.NewValue(tftypes.Number, big.NewFloat(math.MaxFloat64)),
expected: nil,
},
"MaxFloat64-above": {
in: tftypes.NewValue(tftypes.Number, testMustParseFloat("1.79769313486231570814527423731704356798070e+309")),
expected: diag.Diagnostics{
diag.NewAttributeErrorDiagnostic(
path.Root("test"),
"Float64 Type Validation Error",
fmt.Sprintf("Value %s cannot be represented as a 64-bit floating point.", testMustParseFloat("1.79769313486231570814527423731704356798070e+309")),
),
},
},
}

for name, testCase := range testCases {
name, testCase := name, testCase

t.Run(name, func(t *testing.T) {
t.Parallel()

got := Float64Type{}.Validate(context.Background(), testCase.in, path.Root("test"))

if diff := cmp.Diff(got, testCase.expected); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}
})
}
}

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

Expand Down
18 changes: 16 additions & 2 deletions types/basetypes/float64_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package basetypes
import (
"context"
"fmt"
"math"
"math/big"

"github.com/hashicorp/terraform-plugin-framework/attr"
Expand Down Expand Up @@ -86,9 +87,22 @@ func (t Float64Type) Validate(ctx context.Context, in tftypes.Value, path path.P
return diags
}

_, accuracy := value.Float64()
float64Value, accuracy := value.Float64()

if accuracy != 0 {
// Underflow
// Reference: https://pkg.go.dev/math/big#Float.Float64
if float64Value == 0 && accuracy != big.Exact {
diags.AddAttributeError(
path,
"Float64 Type Validation Error",
fmt.Sprintf("Value %s cannot be represented as a 64-bit floating point.", value),
)
return diags
}

// Overflow
// Reference: https://pkg.go.dev/math/big#Float.Float64
if math.IsInf(float64Value, 0) {
diags.AddAttributeError(
path,
"Float64 Type Validation Error",
Expand Down

0 comments on commit b90f22a

Please sign in to comment.