From 28e3544145b63ed77476e332762e3eefebf1d327 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 20 May 2022 17:10:06 -0400 Subject: [PATCH 01/16] Add Float validators from 'terraform-provider-awscc'. --- validate/float.go | 173 +++++++++++++++++++++++ validate/float_test.go | 313 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 486 insertions(+) create mode 100644 validate/float.go create mode 100644 validate/float_test.go diff --git a/validate/float.go b/validate/float.go new file mode 100644 index 00000000..0337287f --- /dev/null +++ b/validate/float.go @@ -0,0 +1,173 @@ +package validate + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// floatBetweenValidator validates that an float Attribute's value is in a range. +type floatBetweenValidator struct { + tfsdk.AttributeValidator + + min, max float64 +} + +// Description describes the validation in plain text formatting. +func (validator floatBetweenValidator) Description(_ context.Context) string { + return fmt.Sprintf("value must be between %f and %f", validator.min, validator.max) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (validator floatBetweenValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// Validate performs the validation. +func (validator floatBetweenValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { + f, ok := validateFloat(ctx, request, response) + if !ok { + return + } + + if f < validator.min || f > validator.max { + response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( + request.AttributePath, + "Invalid value", + fmt.Sprintf("expected value to be in the range [%f, %f], got %f", validator.min, validator.max, f), + )) + + return + } +} + +// FloatBetween returns a new float value between validator. +func FloatBetween(min, max float64) tfsdk.AttributeValidator { + if min > max { + return nil + } + + return floatBetweenValidator{ + min: min, + max: max, + } +} + +// floatAtLeastValidator validates that an float Attribute's value is at least a certain value. +type floatAtLeastValidator struct { + tfsdk.AttributeValidator + + min float64 +} + +// Description describes the validation in plain text formatting. +func (validator floatAtLeastValidator) Description(_ context.Context) string { + return fmt.Sprintf("value must be at least %f", validator.min) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (validator floatAtLeastValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// Validate performs the validation. +func (validator floatAtLeastValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { + f, ok := validateFloat(ctx, request, response) + if !ok { + return + } + + if f < validator.min { + response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( + request.AttributePath, + "Invalid value", + fmt.Sprintf("expected value to be at least %f, got %f", validator.min, f), + )) + + return + } +} + +// FloatAtLeast returns a new float value at least validator. +func FloatAtLeast(min float64) tfsdk.AttributeValidator { + return floatAtLeastValidator{ + min: min, + } +} + +// floatAtMostValidator validates that an float Attribute's value is at most a certain value. +type floatAtMostValidator struct { + tfsdk.AttributeValidator + + max float64 +} + +// Description describes the validation in plain text formatting. +func (validator floatAtMostValidator) Description(_ context.Context) string { + return fmt.Sprintf("value must be at most %f", validator.max) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (validator floatAtMostValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// Validate performs the validation. +func (validator floatAtMostValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { + f, ok := validateFloat(ctx, request, response) + if !ok { + return + } + + if f > validator.max { + response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( + request.AttributePath, + "Invalid value", + fmt.Sprintf("expected value to be at most %f, got %f", validator.max, f), + )) + + return + } +} + +// FloatAtMost returns a new float value at nost validator. +func FloatAtMost(max float64) tfsdk.AttributeValidator { + return floatAtMostValidator{ + max: max, + } +} + +func validateFloat(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) (float64, bool) { + var n types.Float64 + + diags := tfsdk.ValueAs(ctx, request.AttributeConfig, &n) + + if diags.HasError() { + var n types.Number + + diags := tfsdk.ValueAs(ctx, request.AttributeConfig, &n) + + if diags.HasError() { + response.Diagnostics = append(response.Diagnostics, diags...) + + return 0, false + } else { + if n.Unknown || n.Null { + return 0, false + } + + f, _ := n.Value.Float64() + + return f, true + } + } else { + if n.Unknown || n.Null { + return 0, false + } + + return n.Value, true + } +} diff --git a/validate/float_test.go b/validate/float_test.go new file mode 100644 index 00000000..515ab1d7 --- /dev/null +++ b/validate/float_test.go @@ -0,0 +1,313 @@ +package validate + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestFloatBetweenValidator(t *testing.T) { + t.Parallel() + + type testCase struct { + val tftypes.Value + f func(context.Context, tftypes.Value) (attr.Value, error) + min float64 + max float64 + expectError bool + } + tests := map[string]testCase{ + "not a number": { + val: tftypes.NewValue(tftypes.Bool, true), + f: types.BoolType.ValueFromTerraform, + expectError: true, + }, + "unknown number": { + val: tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "null number": { + val: tftypes.NewValue(tftypes.Number, nil), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "valid integer as Number": { + val: tftypes.NewValue(tftypes.Number, 2), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "valid integer as Float64": { + val: tftypes.NewValue(tftypes.Number, 2), + f: types.Float64Type.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "valid float as Number": { + val: tftypes.NewValue(tftypes.Number, 2.2), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "valid float as Float64": { + val: tftypes.NewValue(tftypes.Number, 2.2), + f: types.Float64Type.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "valid float as Number min": { + val: tftypes.NewValue(tftypes.Number, 0.9), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "valid float as Float64 min": { + val: tftypes.NewValue(tftypes.Number, 0.9), + f: types.Float64Type.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "valid float as Number max": { + val: tftypes.NewValue(tftypes.Number, 3.10), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "valid float as Float64 max": { + val: tftypes.NewValue(tftypes.Number, 3.10), + f: types.Float64Type.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "too small float as Number": { + val: tftypes.NewValue(tftypes.Number, -1.1111), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + max: 3.10, + expectError: true, + }, + "too large float as Number": { + val: tftypes.NewValue(tftypes.Number, 4.2), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + max: 3.10, + expectError: true, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + ctx := context.TODO() + val, err := test.f(ctx, test.val) + + if err != nil { + t.Fatalf("got unexpected error: %s", err) + } + + request := tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + AttributeConfig: val, + } + response := tfsdk.ValidateAttributeResponse{} + FloatBetween(test.min, test.max).Validate(ctx, request, &response) + + if !response.Diagnostics.HasError() && test.expectError { + t.Fatal("expected error, got no error") + } + + if response.Diagnostics.HasError() && !test.expectError { + t.Fatalf("got unexpected error: %s", response.Diagnostics) + } + }) + } +} + +func TestFloatAtLeastValidator(t *testing.T) { + t.Parallel() + + type testCase struct { + val tftypes.Value + f func(context.Context, tftypes.Value) (attr.Value, error) + min float64 + expectError bool + } + tests := map[string]testCase{ + "not a number": { + val: tftypes.NewValue(tftypes.Bool, true), + f: types.BoolType.ValueFromTerraform, + expectError: true, + }, + "unknown number": { + val: tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + }, + "null number": { + val: tftypes.NewValue(tftypes.Number, nil), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + }, + "valid integer as Number": { + val: tftypes.NewValue(tftypes.Number, 2), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + }, + "valid integer as Float64": { + val: tftypes.NewValue(tftypes.Number, 2), + f: types.Float64Type.ValueFromTerraform, + min: 0.90, + }, + "valid float as Number": { + val: tftypes.NewValue(tftypes.Number, 2.2), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + }, + "valid float as Float64": { + val: tftypes.NewValue(tftypes.Number, 2.2), + f: types.Float64Type.ValueFromTerraform, + min: 0.90, + }, + "valid float as Number min": { + val: tftypes.NewValue(tftypes.Number, 0.9), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + }, + "valid float as Float64 min": { + val: tftypes.NewValue(tftypes.Number, 0.9), + f: types.Float64Type.ValueFromTerraform, + min: 0.90, + }, + "too small float as Number": { + val: tftypes.NewValue(tftypes.Number, -1.1111), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + expectError: true, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + ctx := context.TODO() + val, err := test.f(ctx, test.val) + + if err != nil { + t.Fatalf("got unexpected error: %s", err) + } + + request := tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + AttributeConfig: val, + } + response := tfsdk.ValidateAttributeResponse{} + FloatAtLeast(test.min).Validate(ctx, request, &response) + + if !response.Diagnostics.HasError() && test.expectError { + t.Fatal("expected error, got no error") + } + + if response.Diagnostics.HasError() && !test.expectError { + t.Fatalf("got unexpected error: %s", response.Diagnostics) + } + }) + } +} + +func TestFloatAtMostValidator(t *testing.T) { + t.Parallel() + + type testCase struct { + val tftypes.Value + f func(context.Context, tftypes.Value) (attr.Value, error) + max float64 + expectError bool + } + tests := map[string]testCase{ + "not a number": { + val: tftypes.NewValue(tftypes.Bool, true), + f: types.BoolType.ValueFromTerraform, + expectError: true, + }, + "unknown number": { + val: tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), + f: types.NumberType.ValueFromTerraform, + max: 2.00, + }, + "null number": { + val: tftypes.NewValue(tftypes.Number, nil), + f: types.NumberType.ValueFromTerraform, + max: 2.00, + }, + "valid integer as Number": { + val: tftypes.NewValue(tftypes.Number, 1), + f: types.NumberType.ValueFromTerraform, + max: 2.00, + }, + "valid integer as Float64": { + val: tftypes.NewValue(tftypes.Number, 1), + f: types.Float64Type.ValueFromTerraform, + max: 2.00, + }, + "valid float as Number": { + val: tftypes.NewValue(tftypes.Number, 1.1), + f: types.NumberType.ValueFromTerraform, + max: 2.00, + }, + "valid float as Float64": { + val: tftypes.NewValue(tftypes.Number, 1.1), + f: types.Float64Type.ValueFromTerraform, + max: 2.00, + }, + "valid float as Number max": { + val: tftypes.NewValue(tftypes.Number, 2.00), + f: types.NumberType.ValueFromTerraform, + max: 2.00, + }, + "valid float as Float64 max": { + val: tftypes.NewValue(tftypes.Number, 2.00), + f: types.Float64Type.ValueFromTerraform, + max: 2.00, + }, + "too large float as Number": { + val: tftypes.NewValue(tftypes.Number, 3.00), + f: types.NumberType.ValueFromTerraform, + max: 2.00, + expectError: true, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + ctx := context.TODO() + val, err := test.f(ctx, test.val) + + if err != nil { + t.Fatalf("got unexpected error: %s", err) + } + + request := tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + AttributeConfig: val, + } + response := tfsdk.ValidateAttributeResponse{} + FloatAtMost(test.max).Validate(ctx, request, &response) + + if !response.Diagnostics.HasError() && test.expectError { + t.Fatal("expected error, got no error") + } + + if response.Diagnostics.HasError() && !test.expectError { + t.Fatalf("got unexpected error: %s", response.Diagnostics) + } + }) + } +} From 332a134b09742ff8833949c112c8fce78ff7cbaa Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 20 May 2022 17:10:21 -0400 Subject: [PATCH 02/16] Run 'go mod tidy -compat=1.17'. --- go.mod | 14 ++++++++++++-- go.sum | 22 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index ff13f1d0..60abb3a9 100644 --- a/go.mod +++ b/go.mod @@ -2,14 +2,24 @@ module github.com/hashicorp/terraform-plugin-framework-validators go 1.17 -require github.com/hashicorp/terraform-plugin-framework v0.8.0 +require ( + github.com/hashicorp/terraform-plugin-framework v0.8.0 + github.com/hashicorp/terraform-plugin-go v0.9.0 +) require ( + github.com/fatih/color v1.7.0 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/hashicorp/terraform-plugin-go v0.9.0 // indirect + github.com/google/go-cmp v0.5.8 // indirect + github.com/hashicorp/go-hclog v1.2.0 // indirect + github.com/hashicorp/terraform-plugin-log v0.4.0 // indirect + github.com/mattn/go-colorable v0.1.4 // indirect + github.com/mattn/go-isatty v0.0.10 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect github.com/vmihailenco/tagparser v0.1.1 // indirect golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect + golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect google.golang.org/appengine v1.6.5 // indirect google.golang.org/protobuf v1.28.0 // indirect ) diff --git a/go.sum b/go.sum index 1587d719..17136e1f 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,7 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -6,16 +9,32 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/terraform-plugin-framework v0.8.0 h1:2nxk+5qAKlGWOrpWZbAZNkO+AoC87l4+9d/rjtQd6Wo= github.com/hashicorp/terraform-plugin-framework v0.8.0/go.mod h1:jUhqrbeI48gAleP8LXzg9jtRH07EAcpwEGQlYmKNIVg= github.com/hashicorp/terraform-plugin-go v0.9.0 h1:FvLY/3z4SNVatPZdoFcyrlNbCar+WyyOTv5X4Tp+WZc= github.com/hashicorp/terraform-plugin-go v0.9.0/go.mod h1:EawBkgjBWNf7jiKnVoyDyF39OSV+u6KUX+Y73EPj3oM= +github.com/hashicorp/terraform-plugin-log v0.4.0 h1:F3eVnm8r2EfQCe2k9blPIiF/r2TT01SHijXnS7bujvc= +github.com/hashicorp/terraform-plugin-log v0.4.0/go.mod h1:9KclxdunFownr4pIm1jdmwKRmE4d6HVG2c9XDq47rpg= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= @@ -26,6 +45,9 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 04d5da64f72d5ea336f5284f161365f1df531240 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 23 May 2022 09:56:36 -0400 Subject: [PATCH 03/16] Rename package 'validate' -> 'float64validator'. --- {validate => float64validator}/float.go | 2 +- {validate => float64validator}/float_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename {validate => float64validator}/float.go (99%) rename {validate => float64validator}/float_test.go (99%) diff --git a/validate/float.go b/float64validator/float.go similarity index 99% rename from validate/float.go rename to float64validator/float.go index 0337287f..54b50bc9 100644 --- a/validate/float.go +++ b/float64validator/float.go @@ -1,4 +1,4 @@ -package validate +package float64validator import ( "context" diff --git a/validate/float_test.go b/float64validator/float_test.go similarity index 99% rename from validate/float_test.go rename to float64validator/float_test.go index 515ab1d7..92f431b6 100644 --- a/validate/float_test.go +++ b/float64validator/float_test.go @@ -1,4 +1,4 @@ -package validate +package float64validator import ( "context" From 03944bdc60c715169afa1d62460f560da1037229 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 23 May 2022 10:09:25 -0400 Subject: [PATCH 04/16] Split each validator into its own source file. --- float64validator/at_least.go | 51 +++++ float64validator/at_least_test.go | 102 ++++++++++ float64validator/at_most.go | 84 ++++++++ float64validator/at_most_test.go | 102 ++++++++++ float64validator/between.go | 56 ++++++ float64validator/between_test.go | 131 +++++++++++++ float64validator/float.go | 173 ----------------- float64validator/float_test.go | 313 ------------------------------ 8 files changed, 526 insertions(+), 486 deletions(-) create mode 100644 float64validator/at_least.go create mode 100644 float64validator/at_least_test.go create mode 100644 float64validator/at_most.go create mode 100644 float64validator/at_most_test.go create mode 100644 float64validator/between.go create mode 100644 float64validator/between_test.go delete mode 100644 float64validator/float.go delete mode 100644 float64validator/float_test.go diff --git a/float64validator/at_least.go b/float64validator/at_least.go new file mode 100644 index 00000000..e8f65d5d --- /dev/null +++ b/float64validator/at_least.go @@ -0,0 +1,51 @@ +package float64validator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +// floatAtLeastValidator validates that an float Attribute's value is at least a certain value. +type floatAtLeastValidator struct { + tfsdk.AttributeValidator + + min float64 +} + +// Description describes the validation in plain text formatting. +func (validator floatAtLeastValidator) Description(_ context.Context) string { + return fmt.Sprintf("value must be at least %f", validator.min) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (validator floatAtLeastValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// Validate performs the validation. +func (validator floatAtLeastValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { + f, ok := validateFloat(ctx, request, response) + if !ok { + return + } + + if f < validator.min { + response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( + request.AttributePath, + "Invalid value", + fmt.Sprintf("expected value to be at least %f, got %f", validator.min, f), + )) + + return + } +} + +// FloatAtLeast returns a new float value at least validator. +func FloatAtLeast(min float64) tfsdk.AttributeValidator { + return floatAtLeastValidator{ + min: min, + } +} diff --git a/float64validator/at_least_test.go b/float64validator/at_least_test.go new file mode 100644 index 00000000..4a22502e --- /dev/null +++ b/float64validator/at_least_test.go @@ -0,0 +1,102 @@ +package float64validator + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestFloatAtLeastValidator(t *testing.T) { + t.Parallel() + + type testCase struct { + val tftypes.Value + f func(context.Context, tftypes.Value) (attr.Value, error) + min float64 + expectError bool + } + tests := map[string]testCase{ + "not a number": { + val: tftypes.NewValue(tftypes.Bool, true), + f: types.BoolType.ValueFromTerraform, + expectError: true, + }, + "unknown number": { + val: tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + }, + "null number": { + val: tftypes.NewValue(tftypes.Number, nil), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + }, + "valid integer as Number": { + val: tftypes.NewValue(tftypes.Number, 2), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + }, + "valid integer as Float64": { + val: tftypes.NewValue(tftypes.Number, 2), + f: types.Float64Type.ValueFromTerraform, + min: 0.90, + }, + "valid float as Number": { + val: tftypes.NewValue(tftypes.Number, 2.2), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + }, + "valid float as Float64": { + val: tftypes.NewValue(tftypes.Number, 2.2), + f: types.Float64Type.ValueFromTerraform, + min: 0.90, + }, + "valid float as Number min": { + val: tftypes.NewValue(tftypes.Number, 0.9), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + }, + "valid float as Float64 min": { + val: tftypes.NewValue(tftypes.Number, 0.9), + f: types.Float64Type.ValueFromTerraform, + min: 0.90, + }, + "too small float as Number": { + val: tftypes.NewValue(tftypes.Number, -1.1111), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + expectError: true, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + ctx := context.TODO() + val, err := test.f(ctx, test.val) + + if err != nil { + t.Fatalf("got unexpected error: %s", err) + } + + request := tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + AttributeConfig: val, + } + response := tfsdk.ValidateAttributeResponse{} + FloatAtLeast(test.min).Validate(ctx, request, &response) + + if !response.Diagnostics.HasError() && test.expectError { + t.Fatal("expected error, got no error") + } + + if response.Diagnostics.HasError() && !test.expectError { + t.Fatalf("got unexpected error: %s", response.Diagnostics) + } + }) + } +} diff --git a/float64validator/at_most.go b/float64validator/at_most.go new file mode 100644 index 00000000..64ff0149 --- /dev/null +++ b/float64validator/at_most.go @@ -0,0 +1,84 @@ +package float64validator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// floatAtMostValidator validates that an float Attribute's value is at most a certain value. +type floatAtMostValidator struct { + tfsdk.AttributeValidator + + max float64 +} + +// Description describes the validation in plain text formatting. +func (validator floatAtMostValidator) Description(_ context.Context) string { + return fmt.Sprintf("value must be at most %f", validator.max) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (validator floatAtMostValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// Validate performs the validation. +func (validator floatAtMostValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { + f, ok := validateFloat(ctx, request, response) + if !ok { + return + } + + if f > validator.max { + response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( + request.AttributePath, + "Invalid value", + fmt.Sprintf("expected value to be at most %f, got %f", validator.max, f), + )) + + return + } +} + +// FloatAtMost returns a new float value at nost validator. +func FloatAtMost(max float64) tfsdk.AttributeValidator { + return floatAtMostValidator{ + max: max, + } +} + +func validateFloat(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) (float64, bool) { + var n types.Float64 + + diags := tfsdk.ValueAs(ctx, request.AttributeConfig, &n) + + if diags.HasError() { + var n types.Number + + diags := tfsdk.ValueAs(ctx, request.AttributeConfig, &n) + + if diags.HasError() { + response.Diagnostics = append(response.Diagnostics, diags...) + + return 0, false + } else { + if n.Unknown || n.Null { + return 0, false + } + + f, _ := n.Value.Float64() + + return f, true + } + } else { + if n.Unknown || n.Null { + return 0, false + } + + return n.Value, true + } +} diff --git a/float64validator/at_most_test.go b/float64validator/at_most_test.go new file mode 100644 index 00000000..fccb2503 --- /dev/null +++ b/float64validator/at_most_test.go @@ -0,0 +1,102 @@ +package float64validator + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestFloatAtMostValidator(t *testing.T) { + t.Parallel() + + type testCase struct { + val tftypes.Value + f func(context.Context, tftypes.Value) (attr.Value, error) + max float64 + expectError bool + } + tests := map[string]testCase{ + "not a number": { + val: tftypes.NewValue(tftypes.Bool, true), + f: types.BoolType.ValueFromTerraform, + expectError: true, + }, + "unknown number": { + val: tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), + f: types.NumberType.ValueFromTerraform, + max: 2.00, + }, + "null number": { + val: tftypes.NewValue(tftypes.Number, nil), + f: types.NumberType.ValueFromTerraform, + max: 2.00, + }, + "valid integer as Number": { + val: tftypes.NewValue(tftypes.Number, 1), + f: types.NumberType.ValueFromTerraform, + max: 2.00, + }, + "valid integer as Float64": { + val: tftypes.NewValue(tftypes.Number, 1), + f: types.Float64Type.ValueFromTerraform, + max: 2.00, + }, + "valid float as Number": { + val: tftypes.NewValue(tftypes.Number, 1.1), + f: types.NumberType.ValueFromTerraform, + max: 2.00, + }, + "valid float as Float64": { + val: tftypes.NewValue(tftypes.Number, 1.1), + f: types.Float64Type.ValueFromTerraform, + max: 2.00, + }, + "valid float as Number max": { + val: tftypes.NewValue(tftypes.Number, 2.00), + f: types.NumberType.ValueFromTerraform, + max: 2.00, + }, + "valid float as Float64 max": { + val: tftypes.NewValue(tftypes.Number, 2.00), + f: types.Float64Type.ValueFromTerraform, + max: 2.00, + }, + "too large float as Number": { + val: tftypes.NewValue(tftypes.Number, 3.00), + f: types.NumberType.ValueFromTerraform, + max: 2.00, + expectError: true, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + ctx := context.TODO() + val, err := test.f(ctx, test.val) + + if err != nil { + t.Fatalf("got unexpected error: %s", err) + } + + request := tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + AttributeConfig: val, + } + response := tfsdk.ValidateAttributeResponse{} + FloatAtMost(test.max).Validate(ctx, request, &response) + + if !response.Diagnostics.HasError() && test.expectError { + t.Fatal("expected error, got no error") + } + + if response.Diagnostics.HasError() && !test.expectError { + t.Fatalf("got unexpected error: %s", response.Diagnostics) + } + }) + } +} diff --git a/float64validator/between.go b/float64validator/between.go new file mode 100644 index 00000000..305cf029 --- /dev/null +++ b/float64validator/between.go @@ -0,0 +1,56 @@ +package float64validator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +// floatBetweenValidator validates that an float Attribute's value is in a range. +type floatBetweenValidator struct { + tfsdk.AttributeValidator + + min, max float64 +} + +// Description describes the validation in plain text formatting. +func (validator floatBetweenValidator) Description(_ context.Context) string { + return fmt.Sprintf("value must be between %f and %f", validator.min, validator.max) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (validator floatBetweenValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// Validate performs the validation. +func (validator floatBetweenValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { + f, ok := validateFloat(ctx, request, response) + if !ok { + return + } + + if f < validator.min || f > validator.max { + response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( + request.AttributePath, + "Invalid value", + fmt.Sprintf("expected value to be in the range [%f, %f], got %f", validator.min, validator.max, f), + )) + + return + } +} + +// FloatBetween returns a new float value between validator. +func FloatBetween(min, max float64) tfsdk.AttributeValidator { + if min > max { + return nil + } + + return floatBetweenValidator{ + min: min, + max: max, + } +} diff --git a/float64validator/between_test.go b/float64validator/between_test.go new file mode 100644 index 00000000..b4f587b3 --- /dev/null +++ b/float64validator/between_test.go @@ -0,0 +1,131 @@ +package float64validator + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestFloatBetweenValidator(t *testing.T) { + t.Parallel() + + type testCase struct { + val tftypes.Value + f func(context.Context, tftypes.Value) (attr.Value, error) + min float64 + max float64 + expectError bool + } + tests := map[string]testCase{ + "not a number": { + val: tftypes.NewValue(tftypes.Bool, true), + f: types.BoolType.ValueFromTerraform, + expectError: true, + }, + "unknown number": { + val: tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "null number": { + val: tftypes.NewValue(tftypes.Number, nil), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "valid integer as Number": { + val: tftypes.NewValue(tftypes.Number, 2), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "valid integer as Float64": { + val: tftypes.NewValue(tftypes.Number, 2), + f: types.Float64Type.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "valid float as Number": { + val: tftypes.NewValue(tftypes.Number, 2.2), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "valid float as Float64": { + val: tftypes.NewValue(tftypes.Number, 2.2), + f: types.Float64Type.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "valid float as Number min": { + val: tftypes.NewValue(tftypes.Number, 0.9), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "valid float as Float64 min": { + val: tftypes.NewValue(tftypes.Number, 0.9), + f: types.Float64Type.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "valid float as Number max": { + val: tftypes.NewValue(tftypes.Number, 3.10), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "valid float as Float64 max": { + val: tftypes.NewValue(tftypes.Number, 3.10), + f: types.Float64Type.ValueFromTerraform, + min: 0.90, + max: 3.10, + }, + "too small float as Number": { + val: tftypes.NewValue(tftypes.Number, -1.1111), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + max: 3.10, + expectError: true, + }, + "too large float as Number": { + val: tftypes.NewValue(tftypes.Number, 4.2), + f: types.NumberType.ValueFromTerraform, + min: 0.90, + max: 3.10, + expectError: true, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + ctx := context.TODO() + val, err := test.f(ctx, test.val) + + if err != nil { + t.Fatalf("got unexpected error: %s", err) + } + + request := tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + AttributeConfig: val, + } + response := tfsdk.ValidateAttributeResponse{} + FloatBetween(test.min, test.max).Validate(ctx, request, &response) + + if !response.Diagnostics.HasError() && test.expectError { + t.Fatal("expected error, got no error") + } + + if response.Diagnostics.HasError() && !test.expectError { + t.Fatalf("got unexpected error: %s", response.Diagnostics) + } + }) + } +} diff --git a/float64validator/float.go b/float64validator/float.go deleted file mode 100644 index 54b50bc9..00000000 --- a/float64validator/float.go +++ /dev/null @@ -1,173 +0,0 @@ -package float64validator - -import ( - "context" - "fmt" - - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" -) - -// floatBetweenValidator validates that an float Attribute's value is in a range. -type floatBetweenValidator struct { - tfsdk.AttributeValidator - - min, max float64 -} - -// Description describes the validation in plain text formatting. -func (validator floatBetweenValidator) Description(_ context.Context) string { - return fmt.Sprintf("value must be between %f and %f", validator.min, validator.max) -} - -// MarkdownDescription describes the validation in Markdown formatting. -func (validator floatBetweenValidator) MarkdownDescription(ctx context.Context) string { - return validator.Description(ctx) -} - -// Validate performs the validation. -func (validator floatBetweenValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { - f, ok := validateFloat(ctx, request, response) - if !ok { - return - } - - if f < validator.min || f > validator.max { - response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( - request.AttributePath, - "Invalid value", - fmt.Sprintf("expected value to be in the range [%f, %f], got %f", validator.min, validator.max, f), - )) - - return - } -} - -// FloatBetween returns a new float value between validator. -func FloatBetween(min, max float64) tfsdk.AttributeValidator { - if min > max { - return nil - } - - return floatBetweenValidator{ - min: min, - max: max, - } -} - -// floatAtLeastValidator validates that an float Attribute's value is at least a certain value. -type floatAtLeastValidator struct { - tfsdk.AttributeValidator - - min float64 -} - -// Description describes the validation in plain text formatting. -func (validator floatAtLeastValidator) Description(_ context.Context) string { - return fmt.Sprintf("value must be at least %f", validator.min) -} - -// MarkdownDescription describes the validation in Markdown formatting. -func (validator floatAtLeastValidator) MarkdownDescription(ctx context.Context) string { - return validator.Description(ctx) -} - -// Validate performs the validation. -func (validator floatAtLeastValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { - f, ok := validateFloat(ctx, request, response) - if !ok { - return - } - - if f < validator.min { - response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( - request.AttributePath, - "Invalid value", - fmt.Sprintf("expected value to be at least %f, got %f", validator.min, f), - )) - - return - } -} - -// FloatAtLeast returns a new float value at least validator. -func FloatAtLeast(min float64) tfsdk.AttributeValidator { - return floatAtLeastValidator{ - min: min, - } -} - -// floatAtMostValidator validates that an float Attribute's value is at most a certain value. -type floatAtMostValidator struct { - tfsdk.AttributeValidator - - max float64 -} - -// Description describes the validation in plain text formatting. -func (validator floatAtMostValidator) Description(_ context.Context) string { - return fmt.Sprintf("value must be at most %f", validator.max) -} - -// MarkdownDescription describes the validation in Markdown formatting. -func (validator floatAtMostValidator) MarkdownDescription(ctx context.Context) string { - return validator.Description(ctx) -} - -// Validate performs the validation. -func (validator floatAtMostValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { - f, ok := validateFloat(ctx, request, response) - if !ok { - return - } - - if f > validator.max { - response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( - request.AttributePath, - "Invalid value", - fmt.Sprintf("expected value to be at most %f, got %f", validator.max, f), - )) - - return - } -} - -// FloatAtMost returns a new float value at nost validator. -func FloatAtMost(max float64) tfsdk.AttributeValidator { - return floatAtMostValidator{ - max: max, - } -} - -func validateFloat(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) (float64, bool) { - var n types.Float64 - - diags := tfsdk.ValueAs(ctx, request.AttributeConfig, &n) - - if diags.HasError() { - var n types.Number - - diags := tfsdk.ValueAs(ctx, request.AttributeConfig, &n) - - if diags.HasError() { - response.Diagnostics = append(response.Diagnostics, diags...) - - return 0, false - } else { - if n.Unknown || n.Null { - return 0, false - } - - f, _ := n.Value.Float64() - - return f, true - } - } else { - if n.Unknown || n.Null { - return 0, false - } - - return n.Value, true - } -} diff --git a/float64validator/float_test.go b/float64validator/float_test.go deleted file mode 100644 index 92f431b6..00000000 --- a/float64validator/float_test.go +++ /dev/null @@ -1,313 +0,0 @@ -package float64validator - -import ( - "context" - "testing" - - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" -) - -func TestFloatBetweenValidator(t *testing.T) { - t.Parallel() - - type testCase struct { - val tftypes.Value - f func(context.Context, tftypes.Value) (attr.Value, error) - min float64 - max float64 - expectError bool - } - tests := map[string]testCase{ - "not a number": { - val: tftypes.NewValue(tftypes.Bool, true), - f: types.BoolType.ValueFromTerraform, - expectError: true, - }, - "unknown number": { - val: tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), - f: types.NumberType.ValueFromTerraform, - min: 0.90, - max: 3.10, - }, - "null number": { - val: tftypes.NewValue(tftypes.Number, nil), - f: types.NumberType.ValueFromTerraform, - min: 0.90, - max: 3.10, - }, - "valid integer as Number": { - val: tftypes.NewValue(tftypes.Number, 2), - f: types.NumberType.ValueFromTerraform, - min: 0.90, - max: 3.10, - }, - "valid integer as Float64": { - val: tftypes.NewValue(tftypes.Number, 2), - f: types.Float64Type.ValueFromTerraform, - min: 0.90, - max: 3.10, - }, - "valid float as Number": { - val: tftypes.NewValue(tftypes.Number, 2.2), - f: types.NumberType.ValueFromTerraform, - min: 0.90, - max: 3.10, - }, - "valid float as Float64": { - val: tftypes.NewValue(tftypes.Number, 2.2), - f: types.Float64Type.ValueFromTerraform, - min: 0.90, - max: 3.10, - }, - "valid float as Number min": { - val: tftypes.NewValue(tftypes.Number, 0.9), - f: types.NumberType.ValueFromTerraform, - min: 0.90, - max: 3.10, - }, - "valid float as Float64 min": { - val: tftypes.NewValue(tftypes.Number, 0.9), - f: types.Float64Type.ValueFromTerraform, - min: 0.90, - max: 3.10, - }, - "valid float as Number max": { - val: tftypes.NewValue(tftypes.Number, 3.10), - f: types.NumberType.ValueFromTerraform, - min: 0.90, - max: 3.10, - }, - "valid float as Float64 max": { - val: tftypes.NewValue(tftypes.Number, 3.10), - f: types.Float64Type.ValueFromTerraform, - min: 0.90, - max: 3.10, - }, - "too small float as Number": { - val: tftypes.NewValue(tftypes.Number, -1.1111), - f: types.NumberType.ValueFromTerraform, - min: 0.90, - max: 3.10, - expectError: true, - }, - "too large float as Number": { - val: tftypes.NewValue(tftypes.Number, 4.2), - f: types.NumberType.ValueFromTerraform, - min: 0.90, - max: 3.10, - expectError: true, - }, - } - - for name, test := range tests { - name, test := name, test - t.Run(name, func(t *testing.T) { - ctx := context.TODO() - val, err := test.f(ctx, test.val) - - if err != nil { - t.Fatalf("got unexpected error: %s", err) - } - - request := tfsdk.ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - AttributeConfig: val, - } - response := tfsdk.ValidateAttributeResponse{} - FloatBetween(test.min, test.max).Validate(ctx, request, &response) - - if !response.Diagnostics.HasError() && test.expectError { - t.Fatal("expected error, got no error") - } - - if response.Diagnostics.HasError() && !test.expectError { - t.Fatalf("got unexpected error: %s", response.Diagnostics) - } - }) - } -} - -func TestFloatAtLeastValidator(t *testing.T) { - t.Parallel() - - type testCase struct { - val tftypes.Value - f func(context.Context, tftypes.Value) (attr.Value, error) - min float64 - expectError bool - } - tests := map[string]testCase{ - "not a number": { - val: tftypes.NewValue(tftypes.Bool, true), - f: types.BoolType.ValueFromTerraform, - expectError: true, - }, - "unknown number": { - val: tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), - f: types.NumberType.ValueFromTerraform, - min: 0.90, - }, - "null number": { - val: tftypes.NewValue(tftypes.Number, nil), - f: types.NumberType.ValueFromTerraform, - min: 0.90, - }, - "valid integer as Number": { - val: tftypes.NewValue(tftypes.Number, 2), - f: types.NumberType.ValueFromTerraform, - min: 0.90, - }, - "valid integer as Float64": { - val: tftypes.NewValue(tftypes.Number, 2), - f: types.Float64Type.ValueFromTerraform, - min: 0.90, - }, - "valid float as Number": { - val: tftypes.NewValue(tftypes.Number, 2.2), - f: types.NumberType.ValueFromTerraform, - min: 0.90, - }, - "valid float as Float64": { - val: tftypes.NewValue(tftypes.Number, 2.2), - f: types.Float64Type.ValueFromTerraform, - min: 0.90, - }, - "valid float as Number min": { - val: tftypes.NewValue(tftypes.Number, 0.9), - f: types.NumberType.ValueFromTerraform, - min: 0.90, - }, - "valid float as Float64 min": { - val: tftypes.NewValue(tftypes.Number, 0.9), - f: types.Float64Type.ValueFromTerraform, - min: 0.90, - }, - "too small float as Number": { - val: tftypes.NewValue(tftypes.Number, -1.1111), - f: types.NumberType.ValueFromTerraform, - min: 0.90, - expectError: true, - }, - } - - for name, test := range tests { - name, test := name, test - t.Run(name, func(t *testing.T) { - ctx := context.TODO() - val, err := test.f(ctx, test.val) - - if err != nil { - t.Fatalf("got unexpected error: %s", err) - } - - request := tfsdk.ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - AttributeConfig: val, - } - response := tfsdk.ValidateAttributeResponse{} - FloatAtLeast(test.min).Validate(ctx, request, &response) - - if !response.Diagnostics.HasError() && test.expectError { - t.Fatal("expected error, got no error") - } - - if response.Diagnostics.HasError() && !test.expectError { - t.Fatalf("got unexpected error: %s", response.Diagnostics) - } - }) - } -} - -func TestFloatAtMostValidator(t *testing.T) { - t.Parallel() - - type testCase struct { - val tftypes.Value - f func(context.Context, tftypes.Value) (attr.Value, error) - max float64 - expectError bool - } - tests := map[string]testCase{ - "not a number": { - val: tftypes.NewValue(tftypes.Bool, true), - f: types.BoolType.ValueFromTerraform, - expectError: true, - }, - "unknown number": { - val: tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), - f: types.NumberType.ValueFromTerraform, - max: 2.00, - }, - "null number": { - val: tftypes.NewValue(tftypes.Number, nil), - f: types.NumberType.ValueFromTerraform, - max: 2.00, - }, - "valid integer as Number": { - val: tftypes.NewValue(tftypes.Number, 1), - f: types.NumberType.ValueFromTerraform, - max: 2.00, - }, - "valid integer as Float64": { - val: tftypes.NewValue(tftypes.Number, 1), - f: types.Float64Type.ValueFromTerraform, - max: 2.00, - }, - "valid float as Number": { - val: tftypes.NewValue(tftypes.Number, 1.1), - f: types.NumberType.ValueFromTerraform, - max: 2.00, - }, - "valid float as Float64": { - val: tftypes.NewValue(tftypes.Number, 1.1), - f: types.Float64Type.ValueFromTerraform, - max: 2.00, - }, - "valid float as Number max": { - val: tftypes.NewValue(tftypes.Number, 2.00), - f: types.NumberType.ValueFromTerraform, - max: 2.00, - }, - "valid float as Float64 max": { - val: tftypes.NewValue(tftypes.Number, 2.00), - f: types.Float64Type.ValueFromTerraform, - max: 2.00, - }, - "too large float as Number": { - val: tftypes.NewValue(tftypes.Number, 3.00), - f: types.NumberType.ValueFromTerraform, - max: 2.00, - expectError: true, - }, - } - - for name, test := range tests { - name, test := name, test - t.Run(name, func(t *testing.T) { - ctx := context.TODO() - val, err := test.f(ctx, test.val) - - if err != nil { - t.Fatalf("got unexpected error: %s", err) - } - - request := tfsdk.ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - AttributeConfig: val, - } - response := tfsdk.ValidateAttributeResponse{} - FloatAtMost(test.max).Validate(ctx, request, &response) - - if !response.Diagnostics.HasError() && test.expectError { - t.Fatal("expected error, got no error") - } - - if response.Diagnostics.HasError() && !test.expectError { - t.Fatalf("got unexpected error: %s", response.Diagnostics) - } - }) - } -} From 0c6ff1fa32355cdbbc3ab7efd94f12fe3cc4681d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 23 May 2022 10:14:25 -0400 Subject: [PATCH 05/16] Remove 'float'/'Float' from type and function names. --- float64validator/at_least.go | 16 ++++++++-------- float64validator/at_least_test.go | 4 ++-- float64validator/at_most.go | 16 ++++++++-------- float64validator/at_most_test.go | 4 ++-- float64validator/between.go | 16 ++++++++-------- float64validator/between_test.go | 4 ++-- 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/float64validator/at_least.go b/float64validator/at_least.go index e8f65d5d..2892b049 100644 --- a/float64validator/at_least.go +++ b/float64validator/at_least.go @@ -8,25 +8,25 @@ import ( "github.com/hashicorp/terraform-plugin-framework/tfsdk" ) -// floatAtLeastValidator validates that an float Attribute's value is at least a certain value. -type floatAtLeastValidator struct { +// atLeastValidator validates that an float Attribute's value is at least a certain value. +type atLeastValidator struct { tfsdk.AttributeValidator min float64 } // Description describes the validation in plain text formatting. -func (validator floatAtLeastValidator) Description(_ context.Context) string { +func (validator atLeastValidator) Description(_ context.Context) string { return fmt.Sprintf("value must be at least %f", validator.min) } // MarkdownDescription describes the validation in Markdown formatting. -func (validator floatAtLeastValidator) MarkdownDescription(ctx context.Context) string { +func (validator atLeastValidator) MarkdownDescription(ctx context.Context) string { return validator.Description(ctx) } // Validate performs the validation. -func (validator floatAtLeastValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { +func (validator atLeastValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { f, ok := validateFloat(ctx, request, response) if !ok { return @@ -43,9 +43,9 @@ func (validator floatAtLeastValidator) Validate(ctx context.Context, request tfs } } -// FloatAtLeast returns a new float value at least validator. -func FloatAtLeast(min float64) tfsdk.AttributeValidator { - return floatAtLeastValidator{ +// AtLeast returns a new float value at least validator. +func AtLeast(min float64) tfsdk.AttributeValidator { + return atLeastValidator{ min: min, } } diff --git a/float64validator/at_least_test.go b/float64validator/at_least_test.go index 4a22502e..1f307822 100644 --- a/float64validator/at_least_test.go +++ b/float64validator/at_least_test.go @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-go/tftypes" ) -func TestFloatAtLeastValidator(t *testing.T) { +func TestAtLeastValidator(t *testing.T) { t.Parallel() type testCase struct { @@ -88,7 +88,7 @@ func TestFloatAtLeastValidator(t *testing.T) { AttributeConfig: val, } response := tfsdk.ValidateAttributeResponse{} - FloatAtLeast(test.min).Validate(ctx, request, &response) + AtLeast(test.min).Validate(ctx, request, &response) if !response.Diagnostics.HasError() && test.expectError { t.Fatal("expected error, got no error") diff --git a/float64validator/at_most.go b/float64validator/at_most.go index 64ff0149..c3b81bc7 100644 --- a/float64validator/at_most.go +++ b/float64validator/at_most.go @@ -9,25 +9,25 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -// floatAtMostValidator validates that an float Attribute's value is at most a certain value. -type floatAtMostValidator struct { +// atMostValidator validates that an float Attribute's value is at most a certain value. +type atMostValidator struct { tfsdk.AttributeValidator max float64 } // Description describes the validation in plain text formatting. -func (validator floatAtMostValidator) Description(_ context.Context) string { +func (validator atMostValidator) Description(_ context.Context) string { return fmt.Sprintf("value must be at most %f", validator.max) } // MarkdownDescription describes the validation in Markdown formatting. -func (validator floatAtMostValidator) MarkdownDescription(ctx context.Context) string { +func (validator atMostValidator) MarkdownDescription(ctx context.Context) string { return validator.Description(ctx) } // Validate performs the validation. -func (validator floatAtMostValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { +func (validator atMostValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { f, ok := validateFloat(ctx, request, response) if !ok { return @@ -44,9 +44,9 @@ func (validator floatAtMostValidator) Validate(ctx context.Context, request tfsd } } -// FloatAtMost returns a new float value at nost validator. -func FloatAtMost(max float64) tfsdk.AttributeValidator { - return floatAtMostValidator{ +// AtMost returns a new float value at nost validator. +func AtMost(max float64) tfsdk.AttributeValidator { + return atMostValidator{ max: max, } } diff --git a/float64validator/at_most_test.go b/float64validator/at_most_test.go index fccb2503..462faec0 100644 --- a/float64validator/at_most_test.go +++ b/float64validator/at_most_test.go @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-go/tftypes" ) -func TestFloatAtMostValidator(t *testing.T) { +func TestAtMostValidator(t *testing.T) { t.Parallel() type testCase struct { @@ -88,7 +88,7 @@ func TestFloatAtMostValidator(t *testing.T) { AttributeConfig: val, } response := tfsdk.ValidateAttributeResponse{} - FloatAtMost(test.max).Validate(ctx, request, &response) + AtMost(test.max).Validate(ctx, request, &response) if !response.Diagnostics.HasError() && test.expectError { t.Fatal("expected error, got no error") diff --git a/float64validator/between.go b/float64validator/between.go index 305cf029..55633640 100644 --- a/float64validator/between.go +++ b/float64validator/between.go @@ -8,25 +8,25 @@ import ( "github.com/hashicorp/terraform-plugin-framework/tfsdk" ) -// floatBetweenValidator validates that an float Attribute's value is in a range. -type floatBetweenValidator struct { +// betweenValidator validates that an float Attribute's value is in a range. +type betweenValidator struct { tfsdk.AttributeValidator min, max float64 } // Description describes the validation in plain text formatting. -func (validator floatBetweenValidator) Description(_ context.Context) string { +func (validator betweenValidator) Description(_ context.Context) string { return fmt.Sprintf("value must be between %f and %f", validator.min, validator.max) } // MarkdownDescription describes the validation in Markdown formatting. -func (validator floatBetweenValidator) MarkdownDescription(ctx context.Context) string { +func (validator betweenValidator) MarkdownDescription(ctx context.Context) string { return validator.Description(ctx) } // Validate performs the validation. -func (validator floatBetweenValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { +func (validator betweenValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { f, ok := validateFloat(ctx, request, response) if !ok { return @@ -43,13 +43,13 @@ func (validator floatBetweenValidator) Validate(ctx context.Context, request tfs } } -// FloatBetween returns a new float value between validator. -func FloatBetween(min, max float64) tfsdk.AttributeValidator { +// Between returns a new float value between validator. +func Between(min, max float64) tfsdk.AttributeValidator { if min > max { return nil } - return floatBetweenValidator{ + return betweenValidator{ min: min, max: max, } diff --git a/float64validator/between_test.go b/float64validator/between_test.go index b4f587b3..49826821 100644 --- a/float64validator/between_test.go +++ b/float64validator/between_test.go @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-go/tftypes" ) -func TestFloatBetweenValidator(t *testing.T) { +func TestBetweenValidator(t *testing.T) { t.Parallel() type testCase struct { @@ -117,7 +117,7 @@ func TestFloatBetweenValidator(t *testing.T) { AttributeConfig: val, } response := tfsdk.ValidateAttributeResponse{} - FloatBetween(test.min, test.max).Validate(ctx, request, &response) + Between(test.min, test.max).Validate(ctx, request, &response) if !response.Diagnostics.HasError() && test.expectError { t.Fatal("expected error, got no error") From dcb9247684449ba2f3f864055f6005d18a66c154 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 23 May 2022 10:18:26 -0400 Subject: [PATCH 06/16] Use 'var _ tfsdk.AttributeValidator =' syntax to ensure interface implementation. --- float64validator/at_least.go | 4 ++-- float64validator/at_most.go | 4 ++-- float64validator/between.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/float64validator/at_least.go b/float64validator/at_least.go index 2892b049..51d39230 100644 --- a/float64validator/at_least.go +++ b/float64validator/at_least.go @@ -8,10 +8,10 @@ import ( "github.com/hashicorp/terraform-plugin-framework/tfsdk" ) +var _ tfsdk.AttributeValidator = atLeastValidator{} + // atLeastValidator validates that an float Attribute's value is at least a certain value. type atLeastValidator struct { - tfsdk.AttributeValidator - min float64 } diff --git a/float64validator/at_most.go b/float64validator/at_most.go index c3b81bc7..3293f172 100644 --- a/float64validator/at_most.go +++ b/float64validator/at_most.go @@ -9,10 +9,10 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) +var _ tfsdk.AttributeValidator = atMostValidator{} + // atMostValidator validates that an float Attribute's value is at most a certain value. type atMostValidator struct { - tfsdk.AttributeValidator - max float64 } diff --git a/float64validator/between.go b/float64validator/between.go index 55633640..c5e049f2 100644 --- a/float64validator/between.go +++ b/float64validator/between.go @@ -8,10 +8,10 @@ import ( "github.com/hashicorp/terraform-plugin-framework/tfsdk" ) +var _ tfsdk.AttributeValidator = betweenValidator{} + // betweenValidator validates that an float Attribute's value is in a range. type betweenValidator struct { - tfsdk.AttributeValidator - min, max float64 } From f3ccdf98c9d72af5cd8b318b65e9cff0f231e53b Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 23 May 2022 10:22:09 -0400 Subject: [PATCH 07/16] Add 'validatordiag' package. --- validatordiag/diag.go | 29 +++++++++++++++++++++++++ validatordiag/diag_test.go | 43 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 validatordiag/diag.go create mode 100644 validatordiag/diag_test.go diff --git a/validatordiag/diag.go b/validatordiag/diag.go new file mode 100644 index 00000000..047cb8ca --- /dev/null +++ b/validatordiag/diag.go @@ -0,0 +1,29 @@ +package validatordiag + +import ( + "unicode" + "unicode/utf8" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// AttributeValueDiagnostic returns an error Diagnostic to be used when an attribute has an unexpected value. +func AttributeValueDiagnostic(path *tftypes.AttributePath, description string, value string) diag.Diagnostic { + return diag.NewAttributeErrorDiagnostic( + path, + "Invalid Attribute Value", + capitalize(description)+", got: "+value, + ) +} + +// capitalize will uppercase the first letter in a UTF-8 string. +func capitalize(str string) string { + if str == "" { + return "" + } + + firstRune, size := utf8.DecodeRuneInString(str) + + return string(unicode.ToUpper(firstRune)) + str[size:] +} diff --git a/validatordiag/diag_test.go b/validatordiag/diag_test.go new file mode 100644 index 00000000..930a8b18 --- /dev/null +++ b/validatordiag/diag_test.go @@ -0,0 +1,43 @@ +package validatordiag + +import ( + "testing" +) + +func TestCapitalize(t *testing.T) { + t.Parallel() + + type testCase struct { + input string + expected string + } + tests := map[string]testCase{ + "empty string": { + input: "", + expected: "", + }, + "all lowercase": { + input: "abcd", + expected: "Abcd", + }, + "all uppercase": { + input: "ABCD", + expected: "ABCD", + }, + "initial numeric": { + input: "1 ab", + expected: "1 ab", + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + got := capitalize(test.input) + + if got != test.expected { + t.Fatalf("expected: %q, got: %q", test.expected, got) + } + }) + } +} From d63b3d2836ad87a3567e89a43a1832dd93281f6e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 23 May 2022 10:36:07 -0400 Subject: [PATCH 08/16] Use 'validatordiag.AttributeValueDiagnostic'. --- float64validator/at_least.go | 8 ++++---- float64validator/at_most.go | 10 +++++----- float64validator/between.go | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/float64validator/at_least.go b/float64validator/at_least.go index 51d39230..e9d7539e 100644 --- a/float64validator/at_least.go +++ b/float64validator/at_least.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework-validators/validatordiag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" ) @@ -33,10 +33,10 @@ func (validator atLeastValidator) Validate(ctx context.Context, request tfsdk.Va } if f < validator.min { - response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( + response.Diagnostics.Append(validatordiag.AttributeValueDiagnostic( request.AttributePath, - "Invalid value", - fmt.Sprintf("expected value to be at least %f, got %f", validator.min, f), + validator.Description(ctx), + fmt.Sprintf("%f", f), )) return diff --git a/float64validator/at_most.go b/float64validator/at_most.go index 3293f172..232a7986 100644 --- a/float64validator/at_most.go +++ b/float64validator/at_most.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework-validators/validatordiag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -34,17 +34,17 @@ func (validator atMostValidator) Validate(ctx context.Context, request tfsdk.Val } if f > validator.max { - response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( + response.Diagnostics.Append(validatordiag.AttributeValueDiagnostic( request.AttributePath, - "Invalid value", - fmt.Sprintf("expected value to be at most %f, got %f", validator.max, f), + validator.Description(ctx), + fmt.Sprintf("%f", f), )) return } } -// AtMost returns a new float value at nost validator. +// AtMost returns a new float value at most validator. func AtMost(max float64) tfsdk.AttributeValidator { return atMostValidator{ max: max, diff --git a/float64validator/between.go b/float64validator/between.go index c5e049f2..0fb23dff 100644 --- a/float64validator/between.go +++ b/float64validator/between.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework-validators/validatordiag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" ) @@ -33,10 +33,10 @@ func (validator betweenValidator) Validate(ctx context.Context, request tfsdk.Va } if f < validator.min || f > validator.max { - response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( + response.Diagnostics.Append(validatordiag.AttributeValueDiagnostic( request.AttributePath, - "Invalid value", - fmt.Sprintf("expected value to be in the range [%f, %f], got %f", validator.min, validator.max, f), + validator.Description(ctx), + fmt.Sprintf("%f", f), )) return From 23295117fd48d93e1fd8a1a8ad7789611973a718 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 23 May 2022 10:59:52 -0400 Subject: [PATCH 09/16] Use 'attr.Value' for test values, not 'tftypes.Value'. --- float64validator/at_least_test.go | 45 ++++++++----------------- float64validator/at_most_test.go | 45 ++++++++----------------- float64validator/between_test.go | 56 ++++++++++--------------------- 3 files changed, 46 insertions(+), 100 deletions(-) diff --git a/float64validator/at_least_test.go b/float64validator/at_least_test.go index 1f307822..aa86e663 100644 --- a/float64validator/at_least_test.go +++ b/float64validator/at_least_test.go @@ -2,6 +2,7 @@ package float64validator import ( "context" + "math/big" "testing" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -14,60 +15,49 @@ func TestAtLeastValidator(t *testing.T) { t.Parallel() type testCase struct { - val tftypes.Value - f func(context.Context, tftypes.Value) (attr.Value, error) + val attr.Value min float64 expectError bool } tests := map[string]testCase{ "not a number": { - val: tftypes.NewValue(tftypes.Bool, true), - f: types.BoolType.ValueFromTerraform, + val: types.Bool{Value: true}, expectError: true, }, "unknown number": { - val: tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), - f: types.NumberType.ValueFromTerraform, + val: types.Float64{Unknown: true}, min: 0.90, }, "null number": { - val: tftypes.NewValue(tftypes.Number, nil), - f: types.NumberType.ValueFromTerraform, + val: types.Number{Null: true}, min: 0.90, }, "valid integer as Number": { - val: tftypes.NewValue(tftypes.Number, 2), - f: types.NumberType.ValueFromTerraform, + val: types.Number{Value: big.NewFloat(2)}, min: 0.90, }, "valid integer as Float64": { - val: tftypes.NewValue(tftypes.Number, 2), - f: types.Float64Type.ValueFromTerraform, + val: types.Float64{Value: 2}, min: 0.90, }, "valid float as Number": { - val: tftypes.NewValue(tftypes.Number, 2.2), - f: types.NumberType.ValueFromTerraform, + val: types.Number{Value: big.NewFloat(2.2)}, min: 0.90, }, "valid float as Float64": { - val: tftypes.NewValue(tftypes.Number, 2.2), - f: types.Float64Type.ValueFromTerraform, + val: types.Float64{Value: 2.2}, min: 0.90, }, "valid float as Number min": { - val: tftypes.NewValue(tftypes.Number, 0.9), - f: types.NumberType.ValueFromTerraform, + val: types.Float64{Value: 0.9}, min: 0.90, }, "valid float as Float64 min": { - val: tftypes.NewValue(tftypes.Number, 0.9), - f: types.Float64Type.ValueFromTerraform, + val: types.Float64{Value: 0.9}, min: 0.90, }, "too small float as Number": { - val: tftypes.NewValue(tftypes.Number, -1.1111), - f: types.NumberType.ValueFromTerraform, + val: types.Number{Value: big.NewFloat(-1.1111)}, min: 0.90, expectError: true, }, @@ -76,19 +66,12 @@ func TestAtLeastValidator(t *testing.T) { for name, test := range tests { name, test := name, test t.Run(name, func(t *testing.T) { - ctx := context.TODO() - val, err := test.f(ctx, test.val) - - if err != nil { - t.Fatalf("got unexpected error: %s", err) - } - request := tfsdk.ValidateAttributeRequest{ AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - AttributeConfig: val, + AttributeConfig: test.val, } response := tfsdk.ValidateAttributeResponse{} - AtLeast(test.min).Validate(ctx, request, &response) + AtLeast(test.min).Validate(context.TODO(), request, &response) if !response.Diagnostics.HasError() && test.expectError { t.Fatal("expected error, got no error") diff --git a/float64validator/at_most_test.go b/float64validator/at_most_test.go index 462faec0..999bf49e 100644 --- a/float64validator/at_most_test.go +++ b/float64validator/at_most_test.go @@ -2,6 +2,7 @@ package float64validator import ( "context" + "math/big" "testing" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -14,60 +15,49 @@ func TestAtMostValidator(t *testing.T) { t.Parallel() type testCase struct { - val tftypes.Value - f func(context.Context, tftypes.Value) (attr.Value, error) + val attr.Value max float64 expectError bool } tests := map[string]testCase{ "not a number": { - val: tftypes.NewValue(tftypes.Bool, true), - f: types.BoolType.ValueFromTerraform, + val: types.Bool{Value: true}, expectError: true, }, "unknown number": { - val: tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), - f: types.NumberType.ValueFromTerraform, + val: types.Number{Unknown: true}, max: 2.00, }, "null number": { - val: tftypes.NewValue(tftypes.Number, nil), - f: types.NumberType.ValueFromTerraform, + val: types.Float64{Null: true}, max: 2.00, }, "valid integer as Number": { - val: tftypes.NewValue(tftypes.Number, 1), - f: types.NumberType.ValueFromTerraform, + val: types.Number{Value: big.NewFloat(1)}, max: 2.00, }, "valid integer as Float64": { - val: tftypes.NewValue(tftypes.Number, 1), - f: types.Float64Type.ValueFromTerraform, + val: types.Float64{Value: 1}, max: 2.00, }, "valid float as Number": { - val: tftypes.NewValue(tftypes.Number, 1.1), - f: types.NumberType.ValueFromTerraform, + val: types.Number{Value: big.NewFloat(1.1)}, max: 2.00, }, "valid float as Float64": { - val: tftypes.NewValue(tftypes.Number, 1.1), - f: types.Float64Type.ValueFromTerraform, + val: types.Float64{Value: 1.1}, max: 2.00, }, "valid float as Number max": { - val: tftypes.NewValue(tftypes.Number, 2.00), - f: types.NumberType.ValueFromTerraform, + val: types.Number{Value: big.NewFloat(2.0)}, max: 2.00, }, "valid float as Float64 max": { - val: tftypes.NewValue(tftypes.Number, 2.00), - f: types.Float64Type.ValueFromTerraform, + val: types.Float64{Value: 2.0}, max: 2.00, }, "too large float as Number": { - val: tftypes.NewValue(tftypes.Number, 3.00), - f: types.NumberType.ValueFromTerraform, + val: types.Number{Value: big.NewFloat(3.0)}, max: 2.00, expectError: true, }, @@ -76,19 +66,12 @@ func TestAtMostValidator(t *testing.T) { for name, test := range tests { name, test := name, test t.Run(name, func(t *testing.T) { - ctx := context.TODO() - val, err := test.f(ctx, test.val) - - if err != nil { - t.Fatalf("got unexpected error: %s", err) - } - request := tfsdk.ValidateAttributeRequest{ AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - AttributeConfig: val, + AttributeConfig: test.val, } response := tfsdk.ValidateAttributeResponse{} - AtMost(test.max).Validate(ctx, request, &response) + AtMost(test.max).Validate(context.TODO(), request, &response) if !response.Diagnostics.HasError() && test.expectError { t.Fatal("expected error, got no error") diff --git a/float64validator/between_test.go b/float64validator/between_test.go index 49826821..fe416807 100644 --- a/float64validator/between_test.go +++ b/float64validator/between_test.go @@ -2,6 +2,7 @@ package float64validator import ( "context" + "math/big" "testing" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -14,88 +15,74 @@ func TestBetweenValidator(t *testing.T) { t.Parallel() type testCase struct { - val tftypes.Value - f func(context.Context, tftypes.Value) (attr.Value, error) + val attr.Value min float64 max float64 expectError bool } tests := map[string]testCase{ "not a number": { - val: tftypes.NewValue(tftypes.Bool, true), - f: types.BoolType.ValueFromTerraform, + val: types.Bool{Value: true}, expectError: true, }, "unknown number": { - val: tftypes.NewValue(tftypes.Number, tftypes.UnknownValue), - f: types.NumberType.ValueFromTerraform, + val: types.Float64{Unknown: true}, min: 0.90, max: 3.10, }, "null number": { - val: tftypes.NewValue(tftypes.Number, nil), - f: types.NumberType.ValueFromTerraform, + val: types.Number{Null: true}, min: 0.90, max: 3.10, }, "valid integer as Number": { - val: tftypes.NewValue(tftypes.Number, 2), - f: types.NumberType.ValueFromTerraform, + val: types.Number{Value: big.NewFloat(2)}, min: 0.90, max: 3.10, }, "valid integer as Float64": { - val: tftypes.NewValue(tftypes.Number, 2), - f: types.Float64Type.ValueFromTerraform, + val: types.Float64{Value: 2}, min: 0.90, max: 3.10, }, "valid float as Number": { - val: tftypes.NewValue(tftypes.Number, 2.2), - f: types.NumberType.ValueFromTerraform, + val: types.Number{Value: big.NewFloat(2.2)}, min: 0.90, max: 3.10, }, "valid float as Float64": { - val: tftypes.NewValue(tftypes.Number, 2.2), - f: types.Float64Type.ValueFromTerraform, + val: types.Float64{Value: 2.2}, min: 0.90, max: 3.10, }, "valid float as Number min": { - val: tftypes.NewValue(tftypes.Number, 0.9), - f: types.NumberType.ValueFromTerraform, + val: types.Float64{Value: 0.9}, min: 0.90, max: 3.10, }, "valid float as Float64 min": { - val: tftypes.NewValue(tftypes.Number, 0.9), - f: types.Float64Type.ValueFromTerraform, + val: types.Float64{Value: 0.9}, min: 0.90, max: 3.10, }, "valid float as Number max": { - val: tftypes.NewValue(tftypes.Number, 3.10), - f: types.NumberType.ValueFromTerraform, + val: types.Number{Value: big.NewFloat(3.1)}, min: 0.90, max: 3.10, }, "valid float as Float64 max": { - val: tftypes.NewValue(tftypes.Number, 3.10), - f: types.Float64Type.ValueFromTerraform, + val: types.Float64{Value: 3.1}, min: 0.90, max: 3.10, }, "too small float as Number": { - val: tftypes.NewValue(tftypes.Number, -1.1111), - f: types.NumberType.ValueFromTerraform, + val: types.Number{Value: big.NewFloat(-1.1111)}, min: 0.90, max: 3.10, expectError: true, }, - "too large float as Number": { - val: tftypes.NewValue(tftypes.Number, 4.2), - f: types.NumberType.ValueFromTerraform, + "too large float as Float64": { + val: types.Float64{Value: 4.2}, min: 0.90, max: 3.10, expectError: true, @@ -105,19 +92,12 @@ func TestBetweenValidator(t *testing.T) { for name, test := range tests { name, test := name, test t.Run(name, func(t *testing.T) { - ctx := context.TODO() - val, err := test.f(ctx, test.val) - - if err != nil { - t.Fatalf("got unexpected error: %s", err) - } - request := tfsdk.ValidateAttributeRequest{ AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - AttributeConfig: val, + AttributeConfig: test.val, } response := tfsdk.ValidateAttributeResponse{} - Between(test.min, test.max).Validate(ctx, request, &response) + Between(test.min, test.max).Validate(context.TODO(), request, &response) if !response.Diagnostics.HasError() && test.expectError { t.Fatal("expected error, got no error") From 1f1f3c0e3d0700d6d4f5d2760b150b0ae6f7e805 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 23 May 2022 12:14:18 -0400 Subject: [PATCH 10/16] Unknown and Null float values are not valid. --- float64validator/at_least.go | 7 +++- float64validator/at_least_test.go | 11 ++++-- float64validator/at_most.go | 65 ++++++++++++++++++++++++------- float64validator/at_most_test.go | 11 ++++-- float64validator/between.go | 7 +++- float64validator/between_test.go | 16 +++++--- 6 files changed, 84 insertions(+), 33 deletions(-) diff --git a/float64validator/at_least.go b/float64validator/at_least.go index e9d7539e..d483469f 100644 --- a/float64validator/at_least.go +++ b/float64validator/at_least.go @@ -27,8 +27,11 @@ func (validator atLeastValidator) MarkdownDescription(ctx context.Context) strin // Validate performs the validation. func (validator atLeastValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { - f, ok := validateFloat(ctx, request, response) - if !ok { + f, diags := validateFloat(ctx, validator, request) + + if diags.HasError() { + response.Diagnostics.Append(diags...) + return } diff --git a/float64validator/at_least_test.go b/float64validator/at_least_test.go index aa86e663..4d2aae27 100644 --- a/float64validator/at_least_test.go +++ b/float64validator/at_least_test.go @@ -22,15 +22,18 @@ func TestAtLeastValidator(t *testing.T) { tests := map[string]testCase{ "not a number": { val: types.Bool{Value: true}, + min: 0.90, expectError: true, }, "unknown number": { - val: types.Float64{Unknown: true}, - min: 0.90, + val: types.Float64{Unknown: true}, + min: 0.90, + expectError: true, }, "null number": { - val: types.Number{Null: true}, - min: 0.90, + val: types.Number{Null: true}, + min: 0.90, + expectError: true, }, "valid integer as Number": { val: types.Number{Value: big.NewFloat(2)}, diff --git a/float64validator/at_most.go b/float64validator/at_most.go index 232a7986..e7230c04 100644 --- a/float64validator/at_most.go +++ b/float64validator/at_most.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework-validators/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -28,8 +29,11 @@ func (validator atMostValidator) MarkdownDescription(ctx context.Context) string // Validate performs the validation. func (validator atMostValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { - f, ok := validateFloat(ctx, request, response) - if !ok { + f, diags := validateFloat(ctx, validator, request) + + if diags.HasError() { + response.Diagnostics.Append(diags...) + return } @@ -51,7 +55,7 @@ func AtMost(max float64) tfsdk.AttributeValidator { } } -func validateFloat(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) (float64, bool) { +func validateFloat(ctx context.Context, validator tfsdk.AttributeValidator, request tfsdk.ValidateAttributeRequest) (float64, diag.Diagnostics) { var n types.Float64 diags := tfsdk.ValueAs(ctx, request.AttributeConfig, &n) @@ -62,23 +66,54 @@ func validateFloat(ctx context.Context, request tfsdk.ValidateAttributeRequest, diags := tfsdk.ValueAs(ctx, request.AttributeConfig, &n) if diags.HasError() { - response.Diagnostics = append(response.Diagnostics, diags...) + return 0, diags + } - return 0, false - } else { - if n.Unknown || n.Null { - return 0, false + if n.Unknown { + return 0, []diag.Diagnostic{ + validatordiag.AttributeValueDiagnostic( + request.AttributePath, + validator.Description(ctx), + "Unknown", + ), } + } - f, _ := n.Value.Float64() - - return f, true + if n.Null { + return 0, []diag.Diagnostic{ + validatordiag.AttributeValueDiagnostic( + request.AttributePath, + validator.Description(ctx), + "Null", + ), + } } - } else { - if n.Unknown || n.Null { - return 0, false + + f, _ := n.Value.Float64() + + return f, nil + + } + + if n.Unknown { + return 0, []diag.Diagnostic{ + validatordiag.AttributeValueDiagnostic( + request.AttributePath, + validator.Description(ctx), + "Unknown", + ), } + } - return n.Value, true + if n.Null { + return 0, []diag.Diagnostic{ + validatordiag.AttributeValueDiagnostic( + request.AttributePath, + validator.Description(ctx), + "Null", + ), + } } + + return n.Value, nil } diff --git a/float64validator/at_most_test.go b/float64validator/at_most_test.go index 999bf49e..5028a543 100644 --- a/float64validator/at_most_test.go +++ b/float64validator/at_most_test.go @@ -22,15 +22,18 @@ func TestAtMostValidator(t *testing.T) { tests := map[string]testCase{ "not a number": { val: types.Bool{Value: true}, + max: 2.00, expectError: true, }, "unknown number": { - val: types.Number{Unknown: true}, - max: 2.00, + val: types.Number{Unknown: true}, + max: 2.00, + expectError: true, }, "null number": { - val: types.Float64{Null: true}, - max: 2.00, + val: types.Float64{Null: true}, + max: 2.00, + expectError: true, }, "valid integer as Number": { val: types.Number{Value: big.NewFloat(1)}, diff --git a/float64validator/between.go b/float64validator/between.go index 0fb23dff..8f10c63f 100644 --- a/float64validator/between.go +++ b/float64validator/between.go @@ -27,8 +27,11 @@ func (validator betweenValidator) MarkdownDescription(ctx context.Context) strin // Validate performs the validation. func (validator betweenValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { - f, ok := validateFloat(ctx, request, response) - if !ok { + f, diags := validateFloat(ctx, validator, request) + + if diags.HasError() { + response.Diagnostics.Append(diags...) + return } diff --git a/float64validator/between_test.go b/float64validator/between_test.go index fe416807..149ec0d0 100644 --- a/float64validator/between_test.go +++ b/float64validator/between_test.go @@ -23,17 +23,21 @@ func TestBetweenValidator(t *testing.T) { tests := map[string]testCase{ "not a number": { val: types.Bool{Value: true}, + min: 0.90, + max: 3.10, expectError: true, }, "unknown number": { - val: types.Float64{Unknown: true}, - min: 0.90, - max: 3.10, + val: types.Float64{Unknown: true}, + min: 0.90, + max: 3.10, + expectError: true, }, "null number": { - val: types.Number{Null: true}, - min: 0.90, - max: 3.10, + val: types.Number{Null: true}, + min: 0.90, + max: 3.10, + expectError: true, }, "valid integer as Number": { val: types.Number{Value: big.NewFloat(2)}, From 578816e76f1592697bc4b24b3f7fb0313a67351f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 24 May 2022 09:33:50 -0400 Subject: [PATCH 11/16] Update float64validator/between.go Co-authored-by: Brian Flad --- float64validator/between.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/float64validator/between.go b/float64validator/between.go index 8f10c63f..29649939 100644 --- a/float64validator/between.go +++ b/float64validator/between.go @@ -46,7 +46,13 @@ func (validator betweenValidator) Validate(ctx context.Context, request tfsdk.Va } } -// Between returns a new float value between validator. +// Between returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a number, which can be represented by a 64-bit floating point. +// - Is exclusively greater than the given minimum and less than the given maximum. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. func Between(min, max float64) tfsdk.AttributeValidator { if min > max { return nil From 1adb12c8df2611141528c8c76a004aca53f23046 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 24 May 2022 09:33:58 -0400 Subject: [PATCH 12/16] Update float64validator/at_most.go Co-authored-by: Brian Flad --- float64validator/at_most.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/float64validator/at_most.go b/float64validator/at_most.go index e7230c04..304f4dc7 100644 --- a/float64validator/at_most.go +++ b/float64validator/at_most.go @@ -48,7 +48,13 @@ func (validator atMostValidator) Validate(ctx context.Context, request tfsdk.Val } } -// AtMost returns a new float value at most validator. +// AtMost returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a number, which can be represented by a 64-bit floating point. +// - Is exclusively less than the given maximum. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. func AtMost(max float64) tfsdk.AttributeValidator { return atMostValidator{ max: max, From 9e4e7802e633b0e64c28e9508e8d9c009c72ec86 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 24 May 2022 09:34:04 -0400 Subject: [PATCH 13/16] Update float64validator/at_least.go Co-authored-by: Brian Flad --- float64validator/at_least.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/float64validator/at_least.go b/float64validator/at_least.go index d483469f..bb2336d5 100644 --- a/float64validator/at_least.go +++ b/float64validator/at_least.go @@ -46,7 +46,13 @@ func (validator atLeastValidator) Validate(ctx context.Context, request tfsdk.Va } } -// AtLeast returns a new float value at least validator. +// AtLeast returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a number, which can be represented by a 64-bit floating point. +// - Is exclusively greater than the given minimum. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. func AtLeast(min float64) tfsdk.AttributeValidator { return atLeastValidator{ min: min, From b391503a2287fe7f1ab637ec5d57126e81102070 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 24 May 2022 10:31:03 -0400 Subject: [PATCH 14/16] Revert "Unknown and Null float values are not valid." This reverts commit 1f1f3c0e3d0700d6d4f5d2760b150b0ae6f7e805. --- float64validator/at_least.go | 7 +--- float64validator/at_least_test.go | 11 ++---- float64validator/at_most.go | 65 +++++++------------------------ float64validator/at_most_test.go | 11 ++---- float64validator/between.go | 7 +--- float64validator/between_test.go | 16 +++----- 6 files changed, 33 insertions(+), 84 deletions(-) diff --git a/float64validator/at_least.go b/float64validator/at_least.go index bb2336d5..e7c9fb07 100644 --- a/float64validator/at_least.go +++ b/float64validator/at_least.go @@ -27,11 +27,8 @@ func (validator atLeastValidator) MarkdownDescription(ctx context.Context) strin // Validate performs the validation. func (validator atLeastValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { - f, diags := validateFloat(ctx, validator, request) - - if diags.HasError() { - response.Diagnostics.Append(diags...) - + f, ok := validateFloat(ctx, request, response) + if !ok { return } diff --git a/float64validator/at_least_test.go b/float64validator/at_least_test.go index 4d2aae27..aa86e663 100644 --- a/float64validator/at_least_test.go +++ b/float64validator/at_least_test.go @@ -22,18 +22,15 @@ func TestAtLeastValidator(t *testing.T) { tests := map[string]testCase{ "not a number": { val: types.Bool{Value: true}, - min: 0.90, expectError: true, }, "unknown number": { - val: types.Float64{Unknown: true}, - min: 0.90, - expectError: true, + val: types.Float64{Unknown: true}, + min: 0.90, }, "null number": { - val: types.Number{Null: true}, - min: 0.90, - expectError: true, + val: types.Number{Null: true}, + min: 0.90, }, "valid integer as Number": { val: types.Number{Value: big.NewFloat(2)}, diff --git a/float64validator/at_most.go b/float64validator/at_most.go index 304f4dc7..dceb908d 100644 --- a/float64validator/at_most.go +++ b/float64validator/at_most.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework-validators/validatordiag" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -29,11 +28,8 @@ func (validator atMostValidator) MarkdownDescription(ctx context.Context) string // Validate performs the validation. func (validator atMostValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { - f, diags := validateFloat(ctx, validator, request) - - if diags.HasError() { - response.Diagnostics.Append(diags...) - + f, ok := validateFloat(ctx, request, response) + if !ok { return } @@ -61,7 +57,7 @@ func AtMost(max float64) tfsdk.AttributeValidator { } } -func validateFloat(ctx context.Context, validator tfsdk.AttributeValidator, request tfsdk.ValidateAttributeRequest) (float64, diag.Diagnostics) { +func validateFloat(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) (float64, bool) { var n types.Float64 diags := tfsdk.ValueAs(ctx, request.AttributeConfig, &n) @@ -72,54 +68,23 @@ func validateFloat(ctx context.Context, validator tfsdk.AttributeValidator, requ diags := tfsdk.ValueAs(ctx, request.AttributeConfig, &n) if diags.HasError() { - return 0, diags - } + response.Diagnostics = append(response.Diagnostics, diags...) - if n.Unknown { - return 0, []diag.Diagnostic{ - validatordiag.AttributeValueDiagnostic( - request.AttributePath, - validator.Description(ctx), - "Unknown", - ), + return 0, false + } else { + if n.Unknown || n.Null { + return 0, false } - } - - if n.Null { - return 0, []diag.Diagnostic{ - validatordiag.AttributeValueDiagnostic( - request.AttributePath, - validator.Description(ctx), - "Null", - ), - } - } - f, _ := n.Value.Float64() + f, _ := n.Value.Float64() - return f, nil - - } - - if n.Unknown { - return 0, []diag.Diagnostic{ - validatordiag.AttributeValueDiagnostic( - request.AttributePath, - validator.Description(ctx), - "Unknown", - ), + return f, true } - } - - if n.Null { - return 0, []diag.Diagnostic{ - validatordiag.AttributeValueDiagnostic( - request.AttributePath, - validator.Description(ctx), - "Null", - ), + } else { + if n.Unknown || n.Null { + return 0, false } - } - return n.Value, nil + return n.Value, true + } } diff --git a/float64validator/at_most_test.go b/float64validator/at_most_test.go index 5028a543..999bf49e 100644 --- a/float64validator/at_most_test.go +++ b/float64validator/at_most_test.go @@ -22,18 +22,15 @@ func TestAtMostValidator(t *testing.T) { tests := map[string]testCase{ "not a number": { val: types.Bool{Value: true}, - max: 2.00, expectError: true, }, "unknown number": { - val: types.Number{Unknown: true}, - max: 2.00, - expectError: true, + val: types.Number{Unknown: true}, + max: 2.00, }, "null number": { - val: types.Float64{Null: true}, - max: 2.00, - expectError: true, + val: types.Float64{Null: true}, + max: 2.00, }, "valid integer as Number": { val: types.Number{Value: big.NewFloat(1)}, diff --git a/float64validator/between.go b/float64validator/between.go index 29649939..33d39128 100644 --- a/float64validator/between.go +++ b/float64validator/between.go @@ -27,11 +27,8 @@ func (validator betweenValidator) MarkdownDescription(ctx context.Context) strin // Validate performs the validation. func (validator betweenValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { - f, diags := validateFloat(ctx, validator, request) - - if diags.HasError() { - response.Diagnostics.Append(diags...) - + f, ok := validateFloat(ctx, request, response) + if !ok { return } diff --git a/float64validator/between_test.go b/float64validator/between_test.go index 149ec0d0..fe416807 100644 --- a/float64validator/between_test.go +++ b/float64validator/between_test.go @@ -23,21 +23,17 @@ func TestBetweenValidator(t *testing.T) { tests := map[string]testCase{ "not a number": { val: types.Bool{Value: true}, - min: 0.90, - max: 3.10, expectError: true, }, "unknown number": { - val: types.Float64{Unknown: true}, - min: 0.90, - max: 3.10, - expectError: true, + val: types.Float64{Unknown: true}, + min: 0.90, + max: 3.10, }, "null number": { - val: types.Number{Null: true}, - min: 0.90, - max: 3.10, - expectError: true, + val: types.Number{Null: true}, + min: 0.90, + max: 3.10, }, "valid integer as Number": { val: types.Number{Value: big.NewFloat(2)}, From 43e028f8986cda844c5831889c730e44918081eb Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 24 May 2022 10:38:33 -0400 Subject: [PATCH 15/16] Do not allow 'types.Number'. --- float64validator/at_least.go | 1 + float64validator/at_least_test.go | 25 ++++++----------------- float64validator/at_most.go | 30 +++++++++------------------- float64validator/at_most_test.go | 25 ++++++----------------- float64validator/between.go | 1 + float64validator/between_test.go | 33 ++++++------------------------- 6 files changed, 29 insertions(+), 86 deletions(-) diff --git a/float64validator/at_least.go b/float64validator/at_least.go index e7c9fb07..2a5c89fe 100644 --- a/float64validator/at_least.go +++ b/float64validator/at_least.go @@ -28,6 +28,7 @@ func (validator atLeastValidator) MarkdownDescription(ctx context.Context) strin // Validate performs the validation. func (validator atLeastValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { f, ok := validateFloat(ctx, request, response) + if !ok { return } diff --git a/float64validator/at_least_test.go b/float64validator/at_least_test.go index aa86e663..eec6e429 100644 --- a/float64validator/at_least_test.go +++ b/float64validator/at_least_test.go @@ -2,7 +2,6 @@ package float64validator import ( "context" - "math/big" "testing" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -20,44 +19,32 @@ func TestAtLeastValidator(t *testing.T) { expectError bool } tests := map[string]testCase{ - "not a number": { + "not a Float64": { val: types.Bool{Value: true}, expectError: true, }, - "unknown number": { + "unknown Float64": { val: types.Float64{Unknown: true}, min: 0.90, }, - "null number": { - val: types.Number{Null: true}, - min: 0.90, - }, - "valid integer as Number": { - val: types.Number{Value: big.NewFloat(2)}, + "null Float64": { + val: types.Float64{Null: true}, min: 0.90, }, "valid integer as Float64": { val: types.Float64{Value: 2}, min: 0.90, }, - "valid float as Number": { - val: types.Number{Value: big.NewFloat(2.2)}, - min: 0.90, - }, "valid float as Float64": { val: types.Float64{Value: 2.2}, min: 0.90, }, - "valid float as Number min": { - val: types.Float64{Value: 0.9}, - min: 0.90, - }, "valid float as Float64 min": { val: types.Float64{Value: 0.9}, min: 0.90, }, - "too small float as Number": { - val: types.Number{Value: big.NewFloat(-1.1111)}, + "too small float as Float64": { + val: types.Float64{Value: -1.1111}, min: 0.90, expectError: true, }, diff --git a/float64validator/at_most.go b/float64validator/at_most.go index dceb908d..72bc343a 100644 --- a/float64validator/at_most.go +++ b/float64validator/at_most.go @@ -29,6 +29,7 @@ func (validator atMostValidator) MarkdownDescription(ctx context.Context) string // Validate performs the validation. func (validator atMostValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { f, ok := validateFloat(ctx, request, response) + if !ok { return } @@ -57,34 +58,21 @@ func AtMost(max float64) tfsdk.AttributeValidator { } } +// validateFloat ensures that the request contains a Float64 value. func validateFloat(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) (float64, bool) { var n types.Float64 diags := tfsdk.ValueAs(ctx, request.AttributeConfig, &n) if diags.HasError() { - var n types.Number - - diags := tfsdk.ValueAs(ctx, request.AttributeConfig, &n) - - if diags.HasError() { - response.Diagnostics = append(response.Diagnostics, diags...) - - return 0, false - } else { - if n.Unknown || n.Null { - return 0, false - } + response.Diagnostics = append(response.Diagnostics, diags...) - f, _ := n.Value.Float64() - - return f, true - } - } else { - if n.Unknown || n.Null { - return 0, false - } + return 0, false + } - return n.Value, true + if n.Unknown || n.Null { + return 0, false } + + return n.Value, true } diff --git a/float64validator/at_most_test.go b/float64validator/at_most_test.go index 999bf49e..0ac5e1b5 100644 --- a/float64validator/at_most_test.go +++ b/float64validator/at_most_test.go @@ -2,7 +2,6 @@ package float64validator import ( "context" - "math/big" "testing" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -20,44 +19,32 @@ func TestAtMostValidator(t *testing.T) { expectError bool } tests := map[string]testCase{ - "not a number": { + "not a Float64": { val: types.Bool{Value: true}, expectError: true, }, - "unknown number": { - val: types.Number{Unknown: true}, + "unknown Float64": { + val: types.Float64{Unknown: true}, max: 2.00, }, - "null number": { + "null Float64": { val: types.Float64{Null: true}, max: 2.00, }, - "valid integer as Number": { - val: types.Number{Value: big.NewFloat(1)}, - max: 2.00, - }, "valid integer as Float64": { val: types.Float64{Value: 1}, max: 2.00, }, - "valid float as Number": { - val: types.Number{Value: big.NewFloat(1.1)}, - max: 2.00, - }, "valid float as Float64": { val: types.Float64{Value: 1.1}, max: 2.00, }, - "valid float as Number max": { - val: types.Number{Value: big.NewFloat(2.0)}, - max: 2.00, - }, "valid float as Float64 max": { val: types.Float64{Value: 2.0}, max: 2.00, }, - "too large float as Number": { - val: types.Number{Value: big.NewFloat(3.0)}, + "too large float as Float64": { + val: types.Float64{Value: 3.0}, max: 2.00, expectError: true, }, diff --git a/float64validator/between.go b/float64validator/between.go index 33d39128..40c50d14 100644 --- a/float64validator/between.go +++ b/float64validator/between.go @@ -28,6 +28,7 @@ func (validator betweenValidator) MarkdownDescription(ctx context.Context) strin // Validate performs the validation. func (validator betweenValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { f, ok := validateFloat(ctx, request, response) + if !ok { return } diff --git a/float64validator/between_test.go b/float64validator/between_test.go index fe416807..745666f2 100644 --- a/float64validator/between_test.go +++ b/float64validator/between_test.go @@ -2,7 +2,6 @@ package float64validator import ( "context" - "math/big" "testing" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -21,22 +20,17 @@ func TestBetweenValidator(t *testing.T) { expectError bool } tests := map[string]testCase{ - "not a number": { + "not a Float64": { val: types.Bool{Value: true}, expectError: true, }, - "unknown number": { + "unknown Float64": { val: types.Float64{Unknown: true}, min: 0.90, max: 3.10, }, - "null number": { - val: types.Number{Null: true}, - min: 0.90, - max: 3.10, - }, - "valid integer as Number": { - val: types.Number{Value: big.NewFloat(2)}, + "null Float64": { + val: types.Float64{Null: true}, min: 0.90, max: 3.10, }, @@ -45,38 +39,23 @@ func TestBetweenValidator(t *testing.T) { min: 0.90, max: 3.10, }, - "valid float as Number": { - val: types.Number{Value: big.NewFloat(2.2)}, - min: 0.90, - max: 3.10, - }, "valid float as Float64": { val: types.Float64{Value: 2.2}, min: 0.90, max: 3.10, }, - "valid float as Number min": { - val: types.Float64{Value: 0.9}, - min: 0.90, - max: 3.10, - }, "valid float as Float64 min": { val: types.Float64{Value: 0.9}, min: 0.90, max: 3.10, }, - "valid float as Number max": { - val: types.Number{Value: big.NewFloat(3.1)}, - min: 0.90, - max: 3.10, - }, "valid float as Float64 max": { val: types.Float64{Value: 3.1}, min: 0.90, max: 3.10, }, - "too small float as Number": { - val: types.Number{Value: big.NewFloat(-1.1111)}, + "too small float as Float64": { + val: types.Float64{Value: -1.1111}, min: 0.90, max: 3.10, expectError: true, From fca371060f935000a03f52a952ab2173828461d6 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 24 May 2022 11:32:49 -0400 Subject: [PATCH 16/16] Add CHANGELOG entry. --- .changelog/18.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/18.txt diff --git a/.changelog/18.txt b/.changelog/18.txt new file mode 100644 index 00000000..a64e5852 --- /dev/null +++ b/.changelog/18.txt @@ -0,0 +1,3 @@ +```release-note:feature +Introduced `float64validator` package with `AtLeast()`, `AtMost()`, and `Between()` validation functions +``` \ No newline at end of file