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 diff --git a/float64validator/at_least.go b/float64validator/at_least.go new file mode 100644 index 00000000..2a5c89fe --- /dev/null +++ b/float64validator/at_least.go @@ -0,0 +1,58 @@ +package float64validator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/validatordiag" + "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 { + min float64 +} + +// Description describes the validation in plain text formatting. +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 atLeastValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// 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 + } + + if f < validator.min { + response.Diagnostics.Append(validatordiag.AttributeValueDiagnostic( + request.AttributePath, + validator.Description(ctx), + fmt.Sprintf("%f", f), + )) + + return + } +} + +// 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, + } +} diff --git a/float64validator/at_least_test.go b/float64validator/at_least_test.go new file mode 100644 index 00000000..eec6e429 --- /dev/null +++ b/float64validator/at_least_test.go @@ -0,0 +1,72 @@ +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 TestAtLeastValidator(t *testing.T) { + t.Parallel() + + type testCase struct { + val attr.Value + min float64 + expectError bool + } + tests := map[string]testCase{ + "not a Float64": { + val: types.Bool{Value: true}, + expectError: true, + }, + "unknown Float64": { + val: types.Float64{Unknown: true}, + min: 0.90, + }, + "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 Float64": { + val: types.Float64{Value: 2.2}, + min: 0.90, + }, + "valid float as Float64 min": { + val: types.Float64{Value: 0.9}, + min: 0.90, + }, + "too small float as Float64": { + val: types.Float64{Value: -1.1111}, + min: 0.90, + expectError: true, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + request := tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + AttributeConfig: test.val, + } + response := tfsdk.ValidateAttributeResponse{} + AtLeast(test.min).Validate(context.TODO(), 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..72bc343a --- /dev/null +++ b/float64validator/at_most.go @@ -0,0 +1,78 @@ +package float64validator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "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 { + max float64 +} + +// Description describes the validation in plain text formatting. +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 atMostValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// 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 + } + + if f > validator.max { + response.Diagnostics.Append(validatordiag.AttributeValueDiagnostic( + request.AttributePath, + validator.Description(ctx), + fmt.Sprintf("%f", f), + )) + + return + } +} + +// 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, + } +} + +// 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() { + response.Diagnostics = append(response.Diagnostics, diags...) + + return 0, false + } + + 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..0ac5e1b5 --- /dev/null +++ b/float64validator/at_most_test.go @@ -0,0 +1,72 @@ +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 TestAtMostValidator(t *testing.T) { + t.Parallel() + + type testCase struct { + val attr.Value + max float64 + expectError bool + } + tests := map[string]testCase{ + "not a Float64": { + val: types.Bool{Value: true}, + expectError: true, + }, + "unknown Float64": { + val: types.Float64{Unknown: true}, + max: 2.00, + }, + "null Float64": { + val: types.Float64{Null: true}, + max: 2.00, + }, + "valid integer as Float64": { + val: types.Float64{Value: 1}, + max: 2.00, + }, + "valid float as Float64": { + val: types.Float64{Value: 1.1}, + max: 2.00, + }, + "valid float as Float64 max": { + val: types.Float64{Value: 2.0}, + max: 2.00, + }, + "too large float as Float64": { + val: types.Float64{Value: 3.0}, + max: 2.00, + expectError: true, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + request := tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + AttributeConfig: test.val, + } + response := tfsdk.ValidateAttributeResponse{} + AtMost(test.max).Validate(context.TODO(), 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..40c50d14 --- /dev/null +++ b/float64validator/between.go @@ -0,0 +1,63 @@ +package float64validator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/validatordiag" + "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 { + min, max float64 +} + +// Description describes the validation in plain text formatting. +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 betweenValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// 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 + } + + if f < validator.min || f > validator.max { + response.Diagnostics.Append(validatordiag.AttributeValueDiagnostic( + request.AttributePath, + validator.Description(ctx), + fmt.Sprintf("%f", f), + )) + + return + } +} + +// 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 + } + + return betweenValidator{ + min: min, + max: max, + } +} diff --git a/float64validator/between_test.go b/float64validator/between_test.go new file mode 100644 index 00000000..745666f2 --- /dev/null +++ b/float64validator/between_test.go @@ -0,0 +1,90 @@ +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 TestBetweenValidator(t *testing.T) { + t.Parallel() + + type testCase struct { + val attr.Value + min float64 + max float64 + expectError bool + } + tests := map[string]testCase{ + "not a Float64": { + val: types.Bool{Value: true}, + expectError: true, + }, + "unknown Float64": { + val: types.Float64{Unknown: true}, + min: 0.90, + max: 3.10, + }, + "null Float64": { + val: types.Float64{Null: true}, + min: 0.90, + max: 3.10, + }, + "valid integer as Float64": { + val: types.Float64{Value: 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 Float64 min": { + val: types.Float64{Value: 0.9}, + 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 Float64": { + val: types.Float64{Value: -1.1111}, + min: 0.90, + max: 3.10, + expectError: true, + }, + "too large float as Float64": { + val: types.Float64{Value: 4.2}, + min: 0.90, + max: 3.10, + expectError: true, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + request := tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + AttributeConfig: test.val, + } + response := tfsdk.ValidateAttributeResponse{} + Between(test.min, test.max).Validate(context.TODO(), 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/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= 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) + } + }) + } +}