Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/unreleased/ENHANCEMENTS-20241014-121220.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: ENHANCEMENTS
body: 'all: Implemented parameter interfaces for all value-based validators. This
allows these validators to be used with provider-defined functions.'
time: 2024-10-14T12:12:20.607373-04:00
custom:
Issue: "235"
7 changes: 7 additions & 0 deletions .changes/unreleased/NOTES-20241014-121711.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
kind: NOTES
body: 'all: Previously, creating validators with invalid data would result in a `nil`
value being returned and a panic from `terraform-plugin-framework`. This has been
updated to return an implementation diagnostic referencing the invalid data/validator during config validation.'
time: 2024-10-14T12:17:11.811926-04:00
custom:
Issue: "235"
2 changes: 1 addition & 1 deletion boolvalidator/doc.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

// Package boolvalidator provides validators for types.Bool attributes.
// Package boolvalidator provides validators for types.Bool attributes or function parameters.
package boolvalidator
23 changes: 21 additions & 2 deletions boolvalidator/equals.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ import (
"fmt"

"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatorfuncerr"
"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)

var _ validator.Bool = equalsValidator{}
var _ function.BoolParameterValidator = equalsValidator{}

type equalsValidator struct {
value types.Bool
Expand Down Expand Up @@ -42,9 +45,25 @@ func (v equalsValidator) ValidateBool(ctx context.Context, req validator.BoolReq
}
}

// Equals returns an AttributeValidator which ensures that the configured boolean attribute
func (v equalsValidator) ValidateParameterBool(ctx context.Context, req function.BoolParameterValidatorRequest, resp *function.BoolParameterValidatorResponse) {
if req.Value.IsNull() || req.Value.IsUnknown() {
return
}

value := req.Value

if !value.Equal(v.value) {
resp.Error = validatorfuncerr.InvalidParameterValueMatchFuncError(
req.ArgumentPosition,
v.Description(ctx),
value.String(),
)
}
}

// Equals returns an AttributeValidator which ensures that the configured boolean attribute or function parameter
// matches the given `value`. Null (unconfigured) and unknown (known after apply) values are skipped.
func Equals(value bool) validator.Bool {
func Equals(value bool) equalsValidator {
return equalsValidator{
value: types.BoolValue(value),
}
Expand Down
60 changes: 37 additions & 23 deletions boolvalidator/equals_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ package boolvalidator_test

import (
"context"
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator"
"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)
Expand All @@ -16,53 +18,65 @@ func TestEqualsValidator(t *testing.T) {
t.Parallel()

type testCase struct {
in types.Bool
validator validator.Bool
expErrors int
in types.Bool
equalsValue bool
expectError bool
}

testCases := map[string]testCase{
"simple-match": {
in: types.BoolValue(true),
validator: boolvalidator.Equals(true),
expErrors: 0,
in: types.BoolValue(true),
equalsValue: true,
},
"simple-mismatch": {
in: types.BoolValue(false),
validator: boolvalidator.Equals(true),
expErrors: 1,
in: types.BoolValue(false),
equalsValue: true,
expectError: true,
},
"skip-validation-on-null": {
in: types.BoolNull(),
validator: boolvalidator.Equals(true),
expErrors: 0,
in: types.BoolNull(),
equalsValue: true,
},
"skip-validation-on-unknown": {
in: types.BoolUnknown(),
validator: boolvalidator.Equals(true),
expErrors: 0,
in: types.BoolUnknown(),
equalsValue: true,
},
}

for name, test := range testCases {
t.Run(name, func(t *testing.T) {
name, test := name, test

t.Run(fmt.Sprintf("ValidateBool - %s", name), func(t *testing.T) {
t.Parallel()
req := validator.BoolRequest{
ConfigValue: test.in,
}
res := validator.BoolResponse{}
test.validator.ValidateBool(context.TODO(), req, &res)
boolvalidator.Equals(test.equalsValue).ValidateBool(context.TODO(), req, &res)

if !res.Diagnostics.HasError() && test.expectError {
t.Fatal("expected error, got no error")
}

if test.expErrors > 0 && !res.Diagnostics.HasError() {
t.Fatalf("expected %d error(s), got none", test.expErrors)
if res.Diagnostics.HasError() && !test.expectError {
t.Fatalf("got unexpected error: %s", res.Diagnostics)
}
})

t.Run(fmt.Sprintf("ValidateParameterBool - %s", name), func(t *testing.T) {
t.Parallel()
req := function.BoolParameterValidatorRequest{
Value: test.in,
}
res := function.BoolParameterValidatorResponse{}
boolvalidator.Equals(test.equalsValue).ValidateParameterBool(context.TODO(), req, &res)

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 res.Error == nil && test.expectError {
t.Fatal("expected error, got no error")
}

if test.expErrors == 0 && res.Diagnostics.HasError() {
t.Fatalf("expected no error(s), got %d: %v", res.Diagnostics.ErrorsCount(), res.Diagnostics)
if res.Error != nil && !test.expectError {
t.Fatalf("got unexpected error: %s", res.Error)
}
})
}
Expand Down
27 changes: 21 additions & 6 deletions float32validator/at_least.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,28 @@ import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"

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

var _ validator.Float32 = atLeastValidator{}
var _ function.Float32ParameterValidator = atLeastValidator{}

// atLeastValidator validates that an float Attribute's value is at least a certain value.
type atLeastValidator struct {
min float32
}

// Description describes the validation in plain text formatting.
func (validator atLeastValidator) Description(_ context.Context) string {
return fmt.Sprintf("value must be at least %f", validator.min)
}

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

// ValidateFloat32 performs the validation.
func (validator atLeastValidator) ValidateFloat32(ctx context.Context, request validator.Float32Request, response *validator.Float32Response) {
if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() {
return
Expand All @@ -46,14 +45,30 @@ func (validator atLeastValidator) ValidateFloat32(ctx context.Context, request v
}
}

func (validator atLeastValidator) ValidateParameterFloat32(ctx context.Context, request function.Float32ParameterValidatorRequest, response *function.Float32ParameterValidatorResponse) {
if request.Value.IsNull() || request.Value.IsUnknown() {
return
}

value := request.Value.ValueFloat32()

if value < validator.min {
response.Error = validatorfuncerr.InvalidParameterValueFuncError(
request.ArgumentPosition,
validator.Description(ctx),
fmt.Sprintf("%f", value),
)
}
}

// AtLeast returns an AttributeValidator which ensures that any configured
// attribute value:
// attribute or function parameter value:
//
// - Is a number, which can be represented by a 32-bit floating point.
// - Is greater than or equal to the given minimum.
//
// Null (unconfigured) and unknown (known after apply) values are skipped.
func AtLeast(minVal float32) validator.Float32 {
func AtLeast(minVal float32) atLeastValidator {
return atLeastValidator{
min: minVal,
}
Expand Down
15 changes: 15 additions & 0 deletions float32validator/at_least_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package float32validator_test

import (
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"

"github.com/hashicorp/terraform-plugin-framework-validators/float32validator"
Expand All @@ -24,3 +25,17 @@ func ExampleAtLeast() {
},
}
}

func ExampleAtLeast_function() {
_ = function.Definition{
Parameters: []function.Parameter{
function.Float32Parameter{
Name: "example_param",
Validators: []function.Float32ParameterValidator{
// Validate floating point value must be at least 42.42
float32validator.AtLeast(42.42),
},
},
},
}
}
22 changes: 21 additions & 1 deletion float32validator/at_least_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ package float32validator_test

import (
"context"
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
Expand Down Expand Up @@ -52,7 +54,8 @@ func TestAtLeastValidator(t *testing.T) {

for name, test := range tests {
name, test := name, test
t.Run(name, func(t *testing.T) {

t.Run(fmt.Sprintf("ValidateFloat32 - %s", name), func(t *testing.T) {
t.Parallel()
request := validator.Float32Request{
Path: path.Root("test"),
Expand All @@ -70,5 +73,22 @@ func TestAtLeastValidator(t *testing.T) {
t.Fatalf("got unexpected error: %s", response.Diagnostics)
}
})

t.Run(fmt.Sprintf("ValidateParameterFloat32 - %s", name), func(t *testing.T) {
t.Parallel()
request := function.Float32ParameterValidatorRequest{
Value: test.val,
}
response := function.Float32ParameterValidatorResponse{}
float32validator.AtLeast(test.min).ValidateParameterFloat32(context.TODO(), request, &response)

if response.Error == nil && test.expectError {
t.Fatal("expected error, got no error")
}

if response.Error != nil && !test.expectError {
t.Fatalf("got unexpected error: %s", response.Error)
}
})
}
}
27 changes: 21 additions & 6 deletions float32validator/at_most.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,28 @@ import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"

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

var _ validator.Float32 = atMostValidator{}
var _ function.Float32ParameterValidator = atMostValidator{}

// atMostValidator validates that an float Attribute's value is at most a certain value.
type atMostValidator struct {
max float32
}

// Description describes the validation in plain text formatting.
func (validator atMostValidator) Description(_ context.Context) string {
return fmt.Sprintf("value must be at most %f", validator.max)
}

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

// ValidateFloat32 performs the validation.
func (v atMostValidator) ValidateFloat32(ctx context.Context, request validator.Float32Request, response *validator.Float32Response) {
if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() {
return
Expand All @@ -46,14 +45,30 @@ func (v atMostValidator) ValidateFloat32(ctx context.Context, request validator.
}
}

func (v atMostValidator) ValidateParameterFloat32(ctx context.Context, request function.Float32ParameterValidatorRequest, response *function.Float32ParameterValidatorResponse) {
if request.Value.IsNull() || request.Value.IsUnknown() {
return
}

value := request.Value.ValueFloat32()

if value > v.max {
response.Error = validatorfuncerr.InvalidParameterValueFuncError(
request.ArgumentPosition,
v.Description(ctx),
fmt.Sprintf("%f", value),
)
}
}

// AtMost returns an AttributeValidator which ensures that any configured
// attribute value:
// attribute or function parameter value:
//
// - Is a number, which can be represented by a 32-bit floating point.
// - Is less than or equal to the given maximum.
//
// Null (unconfigured) and unknown (known after apply) values are skipped.
func AtMost(maxVal float32) validator.Float32 {
func AtMost(maxVal float32) atMostValidator {
return atMostValidator{
max: maxVal,
}
Expand Down
15 changes: 15 additions & 0 deletions float32validator/at_most_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package float32validator_test

import (
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"

"github.com/hashicorp/terraform-plugin-framework-validators/float32validator"
Expand All @@ -24,3 +25,17 @@ func ExampleAtMost() {
},
}
}

func ExampleAtMost_function() {
_ = function.Definition{
Parameters: []function.Parameter{
function.Float32Parameter{
Name: "example_param",
Validators: []function.Float32ParameterValidator{
// Validate floating point value must be at most 42.42
float32validator.AtMost(42.42),
},
},
},
}
}
Loading
Loading