diff --git a/.changes/unreleased/FEATURES-20240920-164852.yaml b/.changes/unreleased/FEATURES-20240920-164852.yaml new file mode 100644 index 0000000..0a90941 --- /dev/null +++ b/.changes/unreleased/FEATURES-20240920-164852.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'boolvalidator: Added `Equals` validator' +time: 2024-09-20T16:48:52.562758-04:00 +custom: + Issue: "232" diff --git a/boolvalidator/equals.go b/boolvalidator/equals.go new file mode 100644 index 0000000..b63da2a --- /dev/null +++ b/boolvalidator/equals.go @@ -0,0 +1,51 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package boolvalidator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ validator.Bool = equalsValidator{} + +type equalsValidator struct { + value types.Bool +} + +func (v equalsValidator) Description(ctx context.Context) string { + return fmt.Sprintf("Value must be %q", v.value) +} + +func (v equalsValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v equalsValidator) ValidateBool(ctx context.Context, req validator.BoolRequest, resp *validator.BoolResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + configValue := req.ConfigValue + + if !configValue.Equal(v.value) { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueMatchDiagnostic( + req.Path, + v.Description(ctx), + configValue.String(), + )) + } +} + +// Equals returns an AttributeValidator which ensures that the configured boolean attribute +// matches the given `value`. Null (unconfigured) and unknown (known after apply) values are skipped. +func Equals(value bool) validator.Bool { + return equalsValidator{ + value: types.BoolValue(value), + } +} diff --git a/boolvalidator/equals_test.go b/boolvalidator/equals_test.go new file mode 100644 index 0000000..8d87af9 --- /dev/null +++ b/boolvalidator/equals_test.go @@ -0,0 +1,69 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package boolvalidator_test + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func TestEqualsValidator(t *testing.T) { + t.Parallel() + + type testCase struct { + in types.Bool + validator validator.Bool + expErrors int + } + + testCases := map[string]testCase{ + "simple-match": { + in: types.BoolValue(true), + validator: boolvalidator.Equals(true), + expErrors: 0, + }, + "simple-mismatch": { + in: types.BoolValue(false), + validator: boolvalidator.Equals(true), + expErrors: 1, + }, + "skip-validation-on-null": { + in: types.BoolNull(), + validator: boolvalidator.Equals(true), + expErrors: 0, + }, + "skip-validation-on-unknown": { + in: types.BoolUnknown(), + validator: boolvalidator.Equals(true), + expErrors: 0, + }, + } + + for name, test := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + req := validator.BoolRequest{ + ConfigValue: test.in, + } + res := validator.BoolResponse{} + test.validator.ValidateBool(context.TODO(), req, &res) + + if test.expErrors > 0 && !res.Diagnostics.HasError() { + t.Fatalf("expected %d error(s), got none", test.expErrors) + } + + if test.expErrors > 0 && test.expErrors != res.Diagnostics.ErrorsCount() { + t.Fatalf("expected %d error(s), got %d: %v", test.expErrors, res.Diagnostics.ErrorsCount(), res.Diagnostics) + } + + if test.expErrors == 0 && res.Diagnostics.HasError() { + t.Fatalf("expected no error(s), got %d: %v", res.Diagnostics.ErrorsCount(), res.Diagnostics) + } + }) + } +}