Skip to content

Commit

Permalink
all: Implement function parameter validation interfaces (#238)
Browse files Browse the repository at this point in the history
* stringvalidator: implement parameter interface

* boolvalidator: implement parameter interface

* float32validator: implement parameter interface

* float64validator: implement parameter interface

* numbervalidator: implement parameter interface

* int32validator: implement parameter interface

* int64validator: implement parameter interface

* listvalidator: implement parameter interface validation

* setvalidator: implement parameter interface

* mapvalidator: implement parameter interface

* add changelogs
  • Loading branch information
austinvalle authored Oct 17, 2024
1 parent 6acd967 commit aa6a2de
Show file tree
Hide file tree
Showing 144 changed files with 3,333 additions and 660 deletions.
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

0 comments on commit aa6a2de

Please sign in to comment.