-
Notifications
You must be signed in to change notification settings - Fork 13
Adding List element validation for ValuesAre #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
62733e1
680e7b2
013ecc8
4c34410
02a900d
58598f8
d237058
1ad4392
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:feature | ||
Introduced `listvalidator` package with `ValuesAre()` validation functions | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,8 @@ | ||
# 0.3.0 (unreleased) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file will be handled prior to release to prevent merge conflicts across PRs: https://github.com/hashicorp/terraform-plugin-framework-validators/blob/main/.github/CONTRIBUTING.md#releases There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed changes from |
||
|
||
FEATURES: | ||
* Introduced `listvalidator` package with `ValuesAre()` validation functions ([#11](https://github.com/hashicorp/terraform-plugin-framework-validators/issues/11)) | ||
|
||
# 0.2.0 (June 7, 2022) | ||
|
||
BREAKING CHANGES: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// Package listvalidator provides validators for types.List attributes. | ||
package listvalidator |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package listvalidator | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/attr" | ||
"github.com/hashicorp/terraform-plugin-framework/tfsdk" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
) | ||
|
||
// validateList ensures that the request contains a List value. | ||
func validateList(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) ([]attr.Value, bool) { | ||
var l types.List | ||
|
||
diags := tfsdk.ValueAs(ctx, request.AttributeConfig, &l) | ||
|
||
if diags.HasError() { | ||
response.Diagnostics = append(response.Diagnostics, diags...) | ||
|
||
return nil, false | ||
} | ||
|
||
if l.Unknown || l.Null { | ||
return nil, false | ||
} | ||
|
||
return l.Elems, true | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package listvalidator | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"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 TestValidateList(t *testing.T) { | ||
t.Parallel() | ||
|
||
testCases := map[string]struct { | ||
request tfsdk.ValidateAttributeRequest | ||
expectedListElems []attr.Value | ||
expectedOk bool | ||
}{ | ||
"invalid-type": { | ||
request: tfsdk.ValidateAttributeRequest{ | ||
AttributeConfig: types.Bool{Value: true}, | ||
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), | ||
}, | ||
expectedListElems: nil, | ||
expectedOk: false, | ||
}, | ||
"list-null": { | ||
request: tfsdk.ValidateAttributeRequest{ | ||
AttributeConfig: types.List{Null: true}, | ||
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), | ||
}, | ||
expectedListElems: nil, | ||
expectedOk: false, | ||
}, | ||
"list-unknown": { | ||
request: tfsdk.ValidateAttributeRequest{ | ||
AttributeConfig: types.List{Unknown: true}, | ||
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), | ||
}, | ||
expectedListElems: nil, | ||
expectedOk: false, | ||
}, | ||
"list-value": { | ||
request: tfsdk.ValidateAttributeRequest{ | ||
AttributeConfig: types.List{ | ||
ElemType: types.StringType, | ||
Elems: []attr.Value{ | ||
types.String{Value: "first"}, | ||
types.String{Value: "second"}, | ||
}, | ||
}, | ||
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), | ||
}, | ||
expectedListElems: []attr.Value{ | ||
types.String{Value: "first"}, | ||
types.String{Value: "second"}, | ||
}, | ||
expectedOk: true, | ||
}, | ||
} | ||
|
||
for name, testCase := range testCases { | ||
name, testCase := name, testCase | ||
|
||
t.Run(name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
gotListElems, gotOk := validateList(context.Background(), testCase.request, &tfsdk.ValidateAttributeResponse{}) | ||
|
||
if diff := cmp.Diff(gotListElems, testCase.expectedListElems); diff != "" { | ||
t.Errorf("unexpected float64 difference: %s", diff) | ||
} | ||
|
||
if diff := cmp.Diff(gotOk, testCase.expectedOk); diff != "" { | ||
t.Errorf("unexpected ok difference: %s", diff) | ||
} | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package listvalidator | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/tfsdk" | ||
) | ||
|
||
var _ tfsdk.AttributeValidator = valuesAreValidator{} | ||
|
||
// valuesAreValidator validates that each list member validates against each of the value validators. | ||
type valuesAreValidator struct { | ||
valueValidators []tfsdk.AttributeValidator | ||
} | ||
|
||
// Description describes the validation in plain text formatting. | ||
func (v valuesAreValidator) Description(ctx context.Context) string { | ||
var descriptions []string | ||
for _, validator := range v.valueValidators { | ||
descriptions = append(descriptions, validator.Description(ctx)) | ||
} | ||
|
||
return fmt.Sprintf("value must satisfy all validations: %s", strings.Join(descriptions, " + ")) | ||
} | ||
|
||
// MarkdownDescription describes the validation in Markdown formatting. | ||
func (v valuesAreValidator) MarkdownDescription(ctx context.Context) string { | ||
return v.Description(ctx) | ||
} | ||
|
||
// Validate performs the validation. | ||
func (v valuesAreValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { | ||
elems, ok := validateList(ctx, req, resp) | ||
if !ok { | ||
return | ||
} | ||
|
||
for k, elem := range elems { | ||
request := tfsdk.ValidateAttributeRequest{ | ||
AttributePath: req.AttributePath.WithElementKeyInt(k), | ||
AttributeConfig: elem, | ||
Config: req.Config, | ||
} | ||
|
||
for _, validator := range v.valueValidators { | ||
validator.Validate(ctx, request, resp) | ||
} | ||
} | ||
} | ||
|
||
// ValuesAre returns an AttributeValidator which ensures that any configured | ||
// attribute value: | ||
// | ||
// - Is a List. | ||
// - That contains list elements, each of which validate against each value validator. | ||
// | ||
// Null (unconfigured) and unknown (known after apply) values are skipped. | ||
func ValuesAre(valueValidators ...tfsdk.AttributeValidator) tfsdk.AttributeValidator { | ||
return valuesAreValidator{ | ||
valueValidators: valueValidators, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package listvalidator | ||
|
||
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" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator" | ||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" | ||
) | ||
|
||
func TestValuesAreValidator(t *testing.T) { | ||
t.Parallel() | ||
|
||
type testCase struct { | ||
val attr.Value | ||
valuesAreValidators []tfsdk.AttributeValidator | ||
expectError bool | ||
} | ||
tests := map[string]testCase{ | ||
"not List": { | ||
val: types.Set{ | ||
ElemType: types.StringType, | ||
}, | ||
expectError: true, | ||
}, | ||
"List unknown": { | ||
val: types.List{ | ||
Unknown: true, | ||
ElemType: types.StringType, | ||
}, | ||
expectError: false, | ||
}, | ||
"List null": { | ||
val: types.List{ | ||
Null: true, | ||
ElemType: types.StringType, | ||
}, | ||
expectError: false, | ||
}, | ||
"List elems invalid": { | ||
val: types.List{ | ||
ElemType: types.StringType, | ||
Elems: []attr.Value{ | ||
types.String{Value: "first"}, | ||
types.String{Value: "second"}, | ||
}, | ||
}, | ||
valuesAreValidators: []tfsdk.AttributeValidator{ | ||
stringvalidator.LengthAtLeast(6), | ||
}, | ||
expectError: true, | ||
}, | ||
"List elems invalid for second validator": { | ||
val: types.List{ | ||
ElemType: types.StringType, | ||
Elems: []attr.Value{ | ||
types.String{Value: "first"}, | ||
types.String{Value: "second"}, | ||
}, | ||
}, | ||
valuesAreValidators: []tfsdk.AttributeValidator{ | ||
stringvalidator.LengthAtLeast(2), | ||
stringvalidator.LengthAtLeast(6), | ||
}, | ||
expectError: true, | ||
}, | ||
"List elems wrong type for validator": { | ||
val: types.List{ | ||
ElemType: types.StringType, | ||
Elems: []attr.Value{ | ||
types.String{Value: "first"}, | ||
types.String{Value: "second"}, | ||
}, | ||
}, | ||
valuesAreValidators: []tfsdk.AttributeValidator{ | ||
int64validator.AtLeast(6), | ||
}, | ||
expectError: true, | ||
}, | ||
"List elems valid": { | ||
val: types.List{ | ||
ElemType: types.StringType, | ||
Elems: []attr.Value{ | ||
types.String{Value: "first"}, | ||
types.String{Value: "second"}, | ||
}, | ||
}, | ||
valuesAreValidators: []tfsdk.AttributeValidator{ | ||
stringvalidator.LengthAtLeast(5), | ||
}, | ||
expectError: false, | ||
}, | ||
} | ||
|
||
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{} | ||
ValuesAre(test.valuesAreValidators...).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) | ||
} | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: typically these files are associated with the PR number, rather than the issue, just so its easy to handle PRs that close multiple issues
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed
11.txt
=>37.txt
.