Skip to content

Commit

Permalink
Adding atLeastSumOf, atMostSumOf and equalToSumOf int64 validators (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
bendbennett committed Jul 13, 2022
1 parent 81783a7 commit 9b4e4c0
Show file tree
Hide file tree
Showing 6 changed files with 679 additions and 0 deletions.
82 changes: 82 additions & 0 deletions int64validator/at_least_sum_of.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package int64validator

import (
"context"
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-go/tftypes"

"github.com/hashicorp/terraform-plugin-framework-validators/validatordiag"
)

var _ tfsdk.AttributeValidator = atLeastSumOfValidator{}

// atLeastSumOfValidator validates that an integer Attribute's value is at least the sum of one
// or more integer Attributes.
type atLeastSumOfValidator struct {
attributesToSumPaths []*tftypes.AttributePath
}

// Description describes the validation in plain text formatting.
func (validator atLeastSumOfValidator) Description(_ context.Context) string {
var attributePaths []string
for _, path := range validator.attributesToSumPaths {
attributePaths = append(attributePaths, path.String())
}

return fmt.Sprintf("value must be at least sum of %s", strings.Join(attributePaths, " + "))
}

// MarkdownDescription describes the validation in Markdown formatting.
func (validator atLeastSumOfValidator) MarkdownDescription(ctx context.Context) string {
return validator.Description(ctx)
}

// Validate performs the validation.
func (validator atLeastSumOfValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) {
i, ok := validateInt(ctx, request, response)

if !ok {
return
}

var sumOfAttribs int64

for _, path := range validator.attributesToSumPaths {
var attribToSum types.Int64

response.Diagnostics.Append(request.Config.GetAttribute(ctx, path, &attribToSum)...)
if response.Diagnostics.HasError() {
return
}

sumOfAttribs += attribToSum.Value
}

if i < sumOfAttribs {

response.Diagnostics.Append(validatordiag.AttributeValueDiagnostic(
request.AttributePath,
validator.Description(ctx),
fmt.Sprintf("%d", i),
))

return
}
}

// AtLeastSumOf returns an AttributeValidator which ensures that any configured
// attribute value:
//
// - Is a number, which can be represented by a 64-bit integer.
// - Is exclusively at least the sum of the given attributes.
//
// Null (unconfigured) and unknown (known after apply) values are skipped.
func AtLeastSumOf(attributesToSum []*tftypes.AttributePath) tfsdk.AttributeValidator {
return atLeastSumOfValidator{
attributesToSumPaths: attributesToSum,
}
}
144 changes: 144 additions & 0 deletions int64validator/at_least_sum_of_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package int64validator

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 TestAtLeastSumOfValidator(t *testing.T) {
t.Parallel()

type testCase struct {
val attr.Value
attributesToSumPaths []*tftypes.AttributePath
requestConfigRaw map[string]tftypes.Value
expectError bool
}
tests := map[string]testCase{
"not an Int64": {
val: types.Bool{Value: true},
expectError: true,
},
"unknown Int64": {
val: types.Int64{Unknown: true},
},
"null Int64": {
val: types.Int64{Null: true},
},
"valid integer as Int64 less than sum of attributes": {
val: types.Int64{Value: 10},
attributesToSumPaths: []*tftypes.AttributePath{
tftypes.NewAttributePath().WithAttributeName("one"),
tftypes.NewAttributePath().WithAttributeName("two"),
},
requestConfigRaw: map[string]tftypes.Value{
"one": tftypes.NewValue(tftypes.Number, 15),
"two": tftypes.NewValue(tftypes.Number, 15),
},
expectError: true,
},
"valid integer as Int64 equal to sum of attributes": {
val: types.Int64{Value: 10},
attributesToSumPaths: []*tftypes.AttributePath{
tftypes.NewAttributePath().WithAttributeName("one"),
tftypes.NewAttributePath().WithAttributeName("two"),
},
requestConfigRaw: map[string]tftypes.Value{
"one": tftypes.NewValue(tftypes.Number, 5),
"two": tftypes.NewValue(tftypes.Number, 5),
},
},
"valid integer as Int64 greater than sum of attributes": {
val: types.Int64{Value: 10},
attributesToSumPaths: []*tftypes.AttributePath{
tftypes.NewAttributePath().WithAttributeName("one"),
tftypes.NewAttributePath().WithAttributeName("two"),
},
requestConfigRaw: map[string]tftypes.Value{
"one": tftypes.NewValue(tftypes.Number, 4),
"two": tftypes.NewValue(tftypes.Number, 4),
},
},
"valid integer as Int64 greater than sum of attributes, when one summed attribute is null": {
val: types.Int64{Value: 10},
attributesToSumPaths: []*tftypes.AttributePath{
tftypes.NewAttributePath().WithAttributeName("one"),
tftypes.NewAttributePath().WithAttributeName("two"),
},
requestConfigRaw: map[string]tftypes.Value{
"one": tftypes.NewValue(tftypes.Number, nil),
"two": tftypes.NewValue(tftypes.Number, 9),
},
},
"valid integer as Int64 does not return error when all attributes are null": {
val: types.Int64{Null: true},
attributesToSumPaths: []*tftypes.AttributePath{
tftypes.NewAttributePath().WithAttributeName("one"),
tftypes.NewAttributePath().WithAttributeName("two"),
},
requestConfigRaw: map[string]tftypes.Value{
"one": tftypes.NewValue(tftypes.Number, nil),
"two": tftypes.NewValue(tftypes.Number, nil),
},
},
"valid integer as Int64 greater than sum of attributes, when one summed attribute is unknown": {
val: types.Int64{Value: 10},
attributesToSumPaths: []*tftypes.AttributePath{
tftypes.NewAttributePath().WithAttributeName("one"),
tftypes.NewAttributePath().WithAttributeName("two"),
},
requestConfigRaw: map[string]tftypes.Value{
"one": tftypes.NewValue(tftypes.Number, tftypes.UnknownValue),
"two": tftypes.NewValue(tftypes.Number, 9),
},
},
"valid integer as Int64 does not return error when all attributes are unknown": {
val: types.Int64{Unknown: true},
attributesToSumPaths: []*tftypes.AttributePath{
tftypes.NewAttributePath().WithAttributeName("one"),
tftypes.NewAttributePath().WithAttributeName("two"),
},
requestConfigRaw: map[string]tftypes.Value{
"one": tftypes.NewValue(tftypes.Number, tftypes.UnknownValue),
"two": tftypes.NewValue(tftypes.Number, tftypes.UnknownValue),
},
},
}

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,
Config: tfsdk.Config{
Raw: tftypes.NewValue(tftypes.Object{}, test.requestConfigRaw),
Schema: tfsdk.Schema{
Attributes: map[string]tfsdk.Attribute{
"test": {Type: types.Int64Type},
"one": {Type: types.Int64Type},
"two": {Type: types.Int64Type},
},
},
},
}

response := tfsdk.ValidateAttributeResponse{}

AtLeastSumOf(test.attributesToSumPaths).Validate(context.Background(), 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)
}
})
}
}
82 changes: 82 additions & 0 deletions int64validator/at_most_sum_of.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package int64validator

import (
"context"
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-go/tftypes"

"github.com/hashicorp/terraform-plugin-framework-validators/validatordiag"
)

var _ tfsdk.AttributeValidator = atMostSumOfValidator{}

// atMostSumOfValidator validates that an integer Attribute's value is at most the sum of one
// or more integer Attributes.
type atMostSumOfValidator struct {
attributesToSumPaths []*tftypes.AttributePath
}

// Description describes the validation in plain text formatting.
func (validator atMostSumOfValidator) Description(_ context.Context) string {
var attributePaths []string
for _, path := range validator.attributesToSumPaths {
attributePaths = append(attributePaths, path.String())
}

return fmt.Sprintf("value must be at most sum of %s", strings.Join(attributePaths, " + "))
}

// MarkdownDescription describes the validation in Markdown formatting.
func (validator atMostSumOfValidator) MarkdownDescription(ctx context.Context) string {
return validator.Description(ctx)
}

// Validate performs the validation.
func (validator atMostSumOfValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) {
i, ok := validateInt(ctx, request, response)

if !ok {
return
}

var sumOfAttribs int64

for _, path := range validator.attributesToSumPaths {
var attribToSum types.Int64

response.Diagnostics.Append(request.Config.GetAttribute(ctx, path, &attribToSum)...)
if response.Diagnostics.HasError() {
return
}

sumOfAttribs += attribToSum.Value
}

if i > sumOfAttribs {

response.Diagnostics.Append(validatordiag.AttributeValueDiagnostic(
request.AttributePath,
validator.Description(ctx),
fmt.Sprintf("%d", i),
))

return
}
}

// AtMostSumOf returns an AttributeValidator which ensures that any configured
// attribute value:
//
// - Is a number, which can be represented by a 64-bit integer.
// - Is exclusively at most the sum of the given attributes.
//
// Null (unconfigured) and unknown (known after apply) values are skipped.
func AtMostSumOf(attributesToSum []*tftypes.AttributePath) tfsdk.AttributeValidator {
return atMostSumOfValidator{
attributesToSumPaths: attributesToSum,
}
}
Loading

0 comments on commit 9b4e4c0

Please sign in to comment.