Skip to content

Commit 5937dfd

Browse files
committed
Adding equalToSumOf int64 validator (#20)
1 parent 5816bae commit 5937dfd

File tree

2 files changed

+238
-0
lines changed

2 files changed

+238
-0
lines changed

int64validator/equal_to_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 = equalToSumOfValidator{}
16+
17+
// equalToSumOfValidator validates that an integer Attribute's value equals the sum of one
18+
// or more integer Attributes.
19+
type equalToSumOfValidator struct {
20+
attributesToSumPaths []*tftypes.AttributePath
21+
}
22+
23+
// Description describes the validation in plain text formatting.
24+
func (validator equalToSumOfValidator) 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 equal to the sum of %s", strings.Join(attributePaths, " + "))
31+
}
32+
33+
// MarkdownDescription describes the validation in Markdown formatting.
34+
func (validator equalToSumOfValidator) MarkdownDescription(ctx context.Context) string {
35+
return validator.Description(ctx)
36+
}
37+
38+
// Validate performs the validation.
39+
func (validator equalToSumOfValidator) 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+
// EqualToSumOf 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 equal to the sum of the given attributes.
76+
//
77+
// Null (unconfigured) and unknown (known after apply) values are skipped.
78+
func EqualToSumOf(attributesToSum []*tftypes.AttributePath) tfsdk.AttributeValidator {
79+
return equalToSumOfValidator{
80+
attributesToSumPaths: attributesToSum,
81+
}
82+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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 TestEqualToSumOfValidator(t *testing.T) {
14+
t.Parallel()
15+
16+
type testCase struct {
17+
val attr.Value
18+
attributesToSumPaths []*tftypes.AttributePath
19+
requestConfigRaw map[string]attr.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 more than sum of attributes": {
34+
val: types.Int64{Value: 11},
35+
attributesToSumPaths: []*tftypes.AttributePath{
36+
tftypes.NewAttributePath().WithAttributeName("one"),
37+
tftypes.NewAttributePath().WithAttributeName("two"),
38+
},
39+
requestConfigRaw: map[string]attr.Value{
40+
"one": types.Int64{Value: 5},
41+
"two": types.Int64{Value: 5},
42+
},
43+
expectError: true,
44+
},
45+
"valid integer as Int64 less than sum of attributes": {
46+
val: types.Int64{Value: 9},
47+
attributesToSumPaths: []*tftypes.AttributePath{
48+
tftypes.NewAttributePath().WithAttributeName("one"),
49+
tftypes.NewAttributePath().WithAttributeName("two"),
50+
},
51+
requestConfigRaw: map[string]attr.Value{
52+
"one": types.Int64{Value: 5},
53+
"two": types.Int64{Value: 5},
54+
},
55+
expectError: true,
56+
},
57+
"valid integer as Int64 equal to sum of attributes": {
58+
val: types.Int64{Value: 10},
59+
attributesToSumPaths: []*tftypes.AttributePath{
60+
tftypes.NewAttributePath().WithAttributeName("one"),
61+
tftypes.NewAttributePath().WithAttributeName("two"),
62+
},
63+
requestConfigRaw: map[string]attr.Value{
64+
"one": types.Int64{Value: 5},
65+
"two": types.Int64{Value: 5},
66+
},
67+
},
68+
"valid integer as Int64 equal to sum of attributes, when one summed attribute is null": {
69+
val: types.Int64{Value: 8},
70+
attributesToSumPaths: []*tftypes.AttributePath{
71+
tftypes.NewAttributePath().WithAttributeName("one"),
72+
tftypes.NewAttributePath().WithAttributeName("two"),
73+
},
74+
requestConfigRaw: map[string]attr.Value{
75+
"one": types.Int64{Null: true},
76+
"two": types.Int64{Value: 8},
77+
},
78+
},
79+
"valid integer as Int64 does not return error when all attributes are null": {
80+
val: types.Int64{Null: true},
81+
attributesToSumPaths: []*tftypes.AttributePath{
82+
tftypes.NewAttributePath().WithAttributeName("one"),
83+
tftypes.NewAttributePath().WithAttributeName("two"),
84+
},
85+
requestConfigRaw: map[string]attr.Value{
86+
"one": types.Int64{Null: true},
87+
"two": types.Int64{Null: true},
88+
},
89+
},
90+
"valid integer as Int64 equal to sum of attributes, when one summed attribute is unknown": {
91+
val: types.Int64{Value: 8},
92+
attributesToSumPaths: []*tftypes.AttributePath{
93+
tftypes.NewAttributePath().WithAttributeName("one"),
94+
tftypes.NewAttributePath().WithAttributeName("two"),
95+
},
96+
requestConfigRaw: map[string]attr.Value{
97+
"one": types.Int64{Unknown: true},
98+
"two": types.Int64{Value: 8},
99+
},
100+
},
101+
"valid integer as Int64 does not return error when all attributes are unknown": {
102+
val: types.Int64{Unknown: true},
103+
attributesToSumPaths: []*tftypes.AttributePath{
104+
tftypes.NewAttributePath().WithAttributeName("one"),
105+
tftypes.NewAttributePath().WithAttributeName("two"),
106+
},
107+
requestConfigRaw: map[string]attr.Value{
108+
"one": types.Int64{Unknown: true},
109+
"two": types.Int64{Unknown: true},
110+
},
111+
},
112+
}
113+
114+
for name, test := range tests {
115+
name, test := name, test
116+
t.Run(name, func(t *testing.T) {
117+
reqConf := make(map[string]tftypes.Value, len(test.requestConfigRaw))
118+
119+
for k, v := range test.requestConfigRaw {
120+
val, err := v.ToTerraformValue(context.Background())
121+
if err != nil {
122+
t.Fatalf("could not attr.Value at key:%s to tftypes.Value", k)
123+
}
124+
125+
reqConf[k] = val
126+
}
127+
128+
request := tfsdk.ValidateAttributeRequest{
129+
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"),
130+
AttributeConfig: test.val,
131+
Config: tfsdk.Config{
132+
Raw: tftypes.NewValue(tftypes.Object{}, reqConf),
133+
Schema: tfsdk.Schema{
134+
Attributes: map[string]tfsdk.Attribute{
135+
"test": {Type: types.Int64Type},
136+
"one": {Type: types.Int64Type},
137+
"two": {Type: types.Int64Type},
138+
},
139+
},
140+
},
141+
}
142+
143+
response := tfsdk.ValidateAttributeResponse{}
144+
145+
EqualToSumOf(test.attributesToSumPaths).Validate(context.Background(), request, &response)
146+
147+
if !response.Diagnostics.HasError() && test.expectError {
148+
t.Fatal("expected error, got no error")
149+
}
150+
151+
if response.Diagnostics.HasError() && !test.expectError {
152+
t.Fatalf("got unexpected error: %s", response.Diagnostics)
153+
}
154+
})
155+
}
156+
}

0 commit comments

Comments
 (0)