Skip to content

Commit 49cdefb

Browse files
committed
Adding atLeastSumOf, atMostSumOf and equalToSumOf int64 validators (#20)
1 parent 7c732e8 commit 49cdefb

File tree

6 files changed

+679
-0
lines changed

6 files changed

+679
-0
lines changed

int64validator/at_least_sum_of.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package int64validator
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
9+
"github.com/hashicorp/terraform-plugin-framework/types"
10+
"github.com/hashicorp/terraform-plugin-go/tftypes"
11+
12+
"github.com/hashicorp/terraform-plugin-framework-validators/validatordiag"
13+
)
14+
15+
var _ tfsdk.AttributeValidator = atLeastSumOfValidator{}
16+
17+
// atLeastSumOfValidator validates that an integer Attribute's value is at least the sum of one
18+
// or more integer Attributes.
19+
type atLeastSumOfValidator struct {
20+
attributesToSumPaths []*tftypes.AttributePath
21+
}
22+
23+
// Description describes the validation in plain text formatting.
24+
func (validator atLeastSumOfValidator) Description(_ context.Context) string {
25+
var attributePaths []string
26+
for _, path := range validator.attributesToSumPaths {
27+
attributePaths = append(attributePaths, path.String())
28+
}
29+
30+
return fmt.Sprintf("value must be at least sum of %s", strings.Join(attributePaths, " + "))
31+
}
32+
33+
// MarkdownDescription describes the validation in Markdown formatting.
34+
func (validator atLeastSumOfValidator) MarkdownDescription(ctx context.Context) string {
35+
return validator.Description(ctx)
36+
}
37+
38+
// Validate performs the validation.
39+
func (validator atLeastSumOfValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) {
40+
i, ok := validateInt(ctx, request, response)
41+
42+
if !ok {
43+
return
44+
}
45+
46+
var sumOfAttribs int64
47+
48+
for _, path := range validator.attributesToSumPaths {
49+
var attribToSum types.Int64
50+
51+
response.Diagnostics.Append(request.Config.GetAttribute(ctx, path, &attribToSum)...)
52+
if response.Diagnostics.HasError() {
53+
return
54+
}
55+
56+
sumOfAttribs += attribToSum.Value
57+
}
58+
59+
if i < sumOfAttribs {
60+
61+
response.Diagnostics.Append(validatordiag.AttributeValueDiagnostic(
62+
request.AttributePath,
63+
validator.Description(ctx),
64+
fmt.Sprintf("%d", i),
65+
))
66+
67+
return
68+
}
69+
}
70+
71+
// AtLeastSumOf returns an AttributeValidator which ensures that any configured
72+
// attribute value:
73+
//
74+
// - Is a number, which can be represented by a 64-bit integer.
75+
// - Is exclusively at least the sum of the given attributes.
76+
//
77+
// Null (unconfigured) and unknown (known after apply) values are skipped.
78+
func AtLeastSumOf(attributesToSum []*tftypes.AttributePath) tfsdk.AttributeValidator {
79+
return atLeastSumOfValidator{
80+
attributesToSumPaths: attributesToSum,
81+
}
82+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package int64validator
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/attr"
8+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
9+
"github.com/hashicorp/terraform-plugin-framework/types"
10+
"github.com/hashicorp/terraform-plugin-go/tftypes"
11+
)
12+
13+
func TestAtLeastSumOfValidator(t *testing.T) {
14+
t.Parallel()
15+
16+
type testCase struct {
17+
val attr.Value
18+
attributesToSumPaths []*tftypes.AttributePath
19+
requestConfigRaw map[string]tftypes.Value
20+
expectError bool
21+
}
22+
tests := map[string]testCase{
23+
"not an Int64": {
24+
val: types.Bool{Value: true},
25+
expectError: true,
26+
},
27+
"unknown Int64": {
28+
val: types.Int64{Unknown: true},
29+
},
30+
"null Int64": {
31+
val: types.Int64{Null: true},
32+
},
33+
"valid integer as Int64 less than sum of attributes": {
34+
val: types.Int64{Value: 10},
35+
attributesToSumPaths: []*tftypes.AttributePath{
36+
tftypes.NewAttributePath().WithAttributeName("one"),
37+
tftypes.NewAttributePath().WithAttributeName("two"),
38+
},
39+
requestConfigRaw: map[string]tftypes.Value{
40+
"one": tftypes.NewValue(tftypes.Number, 15),
41+
"two": tftypes.NewValue(tftypes.Number, 15),
42+
},
43+
expectError: true,
44+
},
45+
"valid integer as Int64 equal to sum of attributes": {
46+
val: types.Int64{Value: 10},
47+
attributesToSumPaths: []*tftypes.AttributePath{
48+
tftypes.NewAttributePath().WithAttributeName("one"),
49+
tftypes.NewAttributePath().WithAttributeName("two"),
50+
},
51+
requestConfigRaw: map[string]tftypes.Value{
52+
"one": tftypes.NewValue(tftypes.Number, 5),
53+
"two": tftypes.NewValue(tftypes.Number, 5),
54+
},
55+
},
56+
"valid integer as Int64 greater than sum of attributes": {
57+
val: types.Int64{Value: 10},
58+
attributesToSumPaths: []*tftypes.AttributePath{
59+
tftypes.NewAttributePath().WithAttributeName("one"),
60+
tftypes.NewAttributePath().WithAttributeName("two"),
61+
},
62+
requestConfigRaw: map[string]tftypes.Value{
63+
"one": tftypes.NewValue(tftypes.Number, 4),
64+
"two": tftypes.NewValue(tftypes.Number, 4),
65+
},
66+
},
67+
"valid integer as Int64 greater than sum of attributes, when one summed attribute is null": {
68+
val: types.Int64{Value: 10},
69+
attributesToSumPaths: []*tftypes.AttributePath{
70+
tftypes.NewAttributePath().WithAttributeName("one"),
71+
tftypes.NewAttributePath().WithAttributeName("two"),
72+
},
73+
requestConfigRaw: map[string]tftypes.Value{
74+
"one": tftypes.NewValue(tftypes.Number, nil),
75+
"two": tftypes.NewValue(tftypes.Number, 9),
76+
},
77+
},
78+
"valid integer as Int64 does not return error when all attributes are null": {
79+
val: types.Int64{Null: true},
80+
attributesToSumPaths: []*tftypes.AttributePath{
81+
tftypes.NewAttributePath().WithAttributeName("one"),
82+
tftypes.NewAttributePath().WithAttributeName("two"),
83+
},
84+
requestConfigRaw: map[string]tftypes.Value{
85+
"one": tftypes.NewValue(tftypes.Number, nil),
86+
"two": tftypes.NewValue(tftypes.Number, nil),
87+
},
88+
},
89+
"valid integer as Int64 greater than sum of attributes, when one summed attribute is unknown": {
90+
val: types.Int64{Value: 10},
91+
attributesToSumPaths: []*tftypes.AttributePath{
92+
tftypes.NewAttributePath().WithAttributeName("one"),
93+
tftypes.NewAttributePath().WithAttributeName("two"),
94+
},
95+
requestConfigRaw: map[string]tftypes.Value{
96+
"one": tftypes.NewValue(tftypes.Number, tftypes.UnknownValue),
97+
"two": tftypes.NewValue(tftypes.Number, 9),
98+
},
99+
},
100+
"valid integer as Int64 does not return error when all attributes are unknown": {
101+
val: types.Int64{Unknown: true},
102+
attributesToSumPaths: []*tftypes.AttributePath{
103+
tftypes.NewAttributePath().WithAttributeName("one"),
104+
tftypes.NewAttributePath().WithAttributeName("two"),
105+
},
106+
requestConfigRaw: map[string]tftypes.Value{
107+
"one": tftypes.NewValue(tftypes.Number, tftypes.UnknownValue),
108+
"two": tftypes.NewValue(tftypes.Number, tftypes.UnknownValue),
109+
},
110+
},
111+
}
112+
113+
for name, test := range tests {
114+
name, test := name, test
115+
t.Run(name, func(t *testing.T) {
116+
request := tfsdk.ValidateAttributeRequest{
117+
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"),
118+
AttributeConfig: test.val,
119+
Config: tfsdk.Config{
120+
Raw: tftypes.NewValue(tftypes.Object{}, test.requestConfigRaw),
121+
Schema: tfsdk.Schema{
122+
Attributes: map[string]tfsdk.Attribute{
123+
"test": {Type: types.Int64Type},
124+
"one": {Type: types.Int64Type},
125+
"two": {Type: types.Int64Type},
126+
},
127+
},
128+
},
129+
}
130+
131+
response := tfsdk.ValidateAttributeResponse{}
132+
133+
AtLeastSumOf(test.attributesToSumPaths).Validate(context.Background(), request, &response)
134+
135+
if !response.Diagnostics.HasError() && test.expectError {
136+
t.Fatal("expected error, got no error")
137+
}
138+
139+
if response.Diagnostics.HasError() && !test.expectError {
140+
t.Fatalf("got unexpected error: %s", response.Diagnostics)
141+
}
142+
})
143+
}
144+
}

int64validator/at_most_sum_of.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package int64validator
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
9+
"github.com/hashicorp/terraform-plugin-framework/types"
10+
"github.com/hashicorp/terraform-plugin-go/tftypes"
11+
12+
"github.com/hashicorp/terraform-plugin-framework-validators/validatordiag"
13+
)
14+
15+
var _ tfsdk.AttributeValidator = atMostSumOfValidator{}
16+
17+
// atMostSumOfValidator validates that an integer Attribute's value is at most the sum of one
18+
// or more integer Attributes.
19+
type atMostSumOfValidator struct {
20+
attributesToSumPaths []*tftypes.AttributePath
21+
}
22+
23+
// Description describes the validation in plain text formatting.
24+
func (validator atMostSumOfValidator) Description(_ context.Context) string {
25+
var attributePaths []string
26+
for _, path := range validator.attributesToSumPaths {
27+
attributePaths = append(attributePaths, path.String())
28+
}
29+
30+
return fmt.Sprintf("value must be at most sum of %s", strings.Join(attributePaths, " + "))
31+
}
32+
33+
// MarkdownDescription describes the validation in Markdown formatting.
34+
func (validator atMostSumOfValidator) MarkdownDescription(ctx context.Context) string {
35+
return validator.Description(ctx)
36+
}
37+
38+
// Validate performs the validation.
39+
func (validator atMostSumOfValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) {
40+
i, ok := validateInt(ctx, request, response)
41+
42+
if !ok {
43+
return
44+
}
45+
46+
var sumOfAttribs int64
47+
48+
for _, path := range validator.attributesToSumPaths {
49+
var attribToSum types.Int64
50+
51+
response.Diagnostics.Append(request.Config.GetAttribute(ctx, path, &attribToSum)...)
52+
if response.Diagnostics.HasError() {
53+
return
54+
}
55+
56+
sumOfAttribs += attribToSum.Value
57+
}
58+
59+
if i > sumOfAttribs {
60+
61+
response.Diagnostics.Append(validatordiag.AttributeValueDiagnostic(
62+
request.AttributePath,
63+
validator.Description(ctx),
64+
fmt.Sprintf("%d", i),
65+
))
66+
67+
return
68+
}
69+
}
70+
71+
// AtMostSumOf returns an AttributeValidator which ensures that any configured
72+
// attribute value:
73+
//
74+
// - Is a number, which can be represented by a 64-bit integer.
75+
// - Is exclusively at most the sum of the given attributes.
76+
//
77+
// Null (unconfigured) and unknown (known after apply) values are skipped.
78+
func AtMostSumOf(attributesToSum []*tftypes.AttributePath) tfsdk.AttributeValidator {
79+
return atMostSumOfValidator{
80+
attributesToSumPaths: attributesToSum,
81+
}
82+
}

0 commit comments

Comments
 (0)