Skip to content

Commit d36ac87

Browse files
SBGoodsbendbennettaustinvalle
authored
Add parameter-based provider-defined function validation (#971)
* Add `function/validator` package * Implement parameter validators in parameter types * Initial implementation of parameter validation logic * Refactor function parameter validators from `function/validator` package to `function` package. * Add parameter unit tests * Add parameter tests to arguments_data_test.go * Switch dynamic parameter test to use `DynamicTypeMust()` * Correct error handling and dynamic type test cases * Add variadic parameter test cases * Add variadic parameter support and test for `tfprotov6` * Add copyright headers * Resolve `forcetypeassert` linter errors * Resolve test failures from merge * Add changelog entries * Skip appending parameter value if validation fails * Support parameter validation for custom types * Refactor parameter validator interface names for clarity. * Add website documentation for parameter-based validation * Add copyright headers * Update changelog entries * Rename `Validate()` method in parameter validator interfaces to `ValidateParameter<Type>()` * Skip appending variadic values if there is an error in validation * Update website/docs/plugin/framework/validation.mdx Co-authored-by: Austin Valle <[email protected]> * Replace `create{type}Value` test helper functions with `New{Type}ValueMust` functions --------- Co-authored-by: Benjamin Bennett <[email protected]> Co-authored-by: Austin Valle <[email protected]>
1 parent f6057df commit d36ac87

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+6212
-22
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
kind: FEATURES
2+
body: '`function`: Add `BoolParameterValidator`, `DynamicParameterValidator`, `Float64ParameterValidator`, `Int64ParameterValidator`,
3+
`ListParameterValidator`, `MapParameterValidator`, `NumberParameterValidator`, `ObjectParameterValidator`, `SetParameterValidator`,
4+
and `StringParameterValidator` interfaces for custom function parameter validation implementations.'
5+
time: 2024-04-05T18:39:17.640444-04:00
6+
custom:
7+
Issue: "971"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
kind: FEATURES
2+
body: '`function`: Add `ParameterWithBoolValidators`, `ParameterWithInt64Validators`,
3+
`ParameterWithFloat64Validators`, `ParameterWithDynamicValidators`, `ParameterWithListValidators`,
4+
`ParameterWithMapValidators`, `ParameterWithNumberValidators`, `ParameterWithObjectValidators`,
5+
`ParameterWithSetValidators`, and `ParameterWithStringValidators` interfaces to enable
6+
parameter-based validation support'
7+
time: 2024-04-05T18:45:27.979266-04:00
8+
custom:
9+
Issue: "971"

function/bool_parameter.go

+10
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
// Ensure the implementation satisifies the desired interfaces.
1212
var _ Parameter = BoolParameter{}
13+
var _ ParameterWithBoolValidators = BoolParameter{}
1314

1415
// BoolParameter represents a function parameter that is a boolean.
1516
//
@@ -70,6 +71,15 @@ type BoolParameter struct {
7071
// alphabetical character and followed by alphanumeric or underscore
7172
// characters.
7273
Name string
74+
75+
// Validators is a list of bool validators that should be applied to the
76+
// parameter.
77+
Validators []BoolParameterValidator
78+
}
79+
80+
// GetValidators returns the list of validators for the parameter.
81+
func (p BoolParameter) GetValidators() []BoolParameterValidator {
82+
return p.Validators
7383
}
7484

7585
// GetAllowNullValue returns if the parameter accepts a null value.

function/bool_parameter_test.go

+44
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import (
77
"testing"
88

99
"github.com/google/go-cmp/cmp"
10+
1011
"github.com/hashicorp/terraform-plugin-framework/attr"
1112
"github.com/hashicorp/terraform-plugin-framework/function"
1213
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
14+
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testvalidator"
1315
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
1416
)
1517

@@ -240,3 +242,45 @@ func TestBoolParameterGetType(t *testing.T) {
240242
})
241243
}
242244
}
245+
246+
func TestBoolParameterBoolValidators(t *testing.T) {
247+
t.Parallel()
248+
249+
testCases := map[string]struct {
250+
parameter function.BoolParameter
251+
expected []function.BoolParameterValidator
252+
}{
253+
"unset": {
254+
parameter: function.BoolParameter{},
255+
expected: nil,
256+
},
257+
"Validators - empty": {
258+
parameter: function.BoolParameter{
259+
Validators: []function.BoolParameterValidator{}},
260+
expected: []function.BoolParameterValidator{},
261+
},
262+
"Validators": {
263+
parameter: function.BoolParameter{
264+
Validators: []function.BoolParameterValidator{
265+
testvalidator.Bool{},
266+
}},
267+
expected: []function.BoolParameterValidator{
268+
testvalidator.Bool{},
269+
},
270+
},
271+
}
272+
273+
for name, testCase := range testCases {
274+
name, testCase := name, testCase
275+
276+
t.Run(name, func(t *testing.T) {
277+
t.Parallel()
278+
279+
got := testCase.parameter.GetValidators()
280+
281+
if diff := cmp.Diff(got, testCase.expected); diff != "" {
282+
t.Errorf("unexpected difference: %s", diff)
283+
}
284+
})
285+
}
286+
}

function/bool_parameter_validator.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package function
5+
6+
import (
7+
"context"
8+
9+
"github.com/hashicorp/terraform-plugin-framework/types"
10+
)
11+
12+
// BoolParameterValidator is a function validator for types.Bool parameters.
13+
type BoolParameterValidator interface {
14+
15+
// ValidateParameterBool performs the validation.
16+
ValidateParameterBool(context.Context, BoolParameterValidatorRequest, *BoolParameterValidatorResponse)
17+
}
18+
19+
// BoolParameterValidatorRequest is a request for types.Bool schema validation.
20+
type BoolParameterValidatorRequest struct {
21+
// ArgumentPosition contains the position of the argument for validation.
22+
// Use this position for any response diagnostics.
23+
ArgumentPosition int64
24+
25+
// Value contains the value of the argument for validation.
26+
Value types.Bool
27+
}
28+
29+
// BoolParameterValidatorResponse is a response to a BoolParameterValidatorRequest.
30+
type BoolParameterValidatorResponse struct {
31+
// Error is a function error generated during validation of the Value.
32+
Error *FuncError
33+
}

function/dynamic_parameter.go

+10
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
// Ensure the implementation satisifies the desired interfaces.
1212
var _ Parameter = DynamicParameter{}
13+
var _ ParameterWithDynamicValidators = DynamicParameter{}
1314

1415
// DynamicParameter represents a function parameter that is a dynamic, rather
1516
// than a static type. Static types are always preferable over dynamic
@@ -65,6 +66,15 @@ type DynamicParameter struct {
6566
// alphabetical character and followed by alphanumeric or underscore
6667
// characters.
6768
Name string
69+
70+
// Validators is a list of dynamic validators that should be applied to the
71+
// parameter.
72+
Validators []DynamicParameterValidator
73+
}
74+
75+
// GetValidators returns the list of validators for the parameter.
76+
func (p DynamicParameter) GetValidators() []DynamicParameterValidator {
77+
return p.Validators
6878
}
6979

7080
// GetAllowNullValue returns if the parameter accepts a null value.

function/dynamic_parameter_test.go

+44
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import (
77
"testing"
88

99
"github.com/google/go-cmp/cmp"
10+
1011
"github.com/hashicorp/terraform-plugin-framework/attr"
1112
"github.com/hashicorp/terraform-plugin-framework/function"
1213
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
14+
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testvalidator"
1315
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
1416
)
1517

@@ -240,3 +242,45 @@ func TestDynamicParameterGetType(t *testing.T) {
240242
})
241243
}
242244
}
245+
246+
func TestDynamicParameterDynamicValidators(t *testing.T) {
247+
t.Parallel()
248+
249+
testCases := map[string]struct {
250+
parameter function.DynamicParameter
251+
expected []function.DynamicParameterValidator
252+
}{
253+
"unset": {
254+
parameter: function.DynamicParameter{},
255+
expected: nil,
256+
},
257+
"Validators - empty": {
258+
parameter: function.DynamicParameter{
259+
Validators: []function.DynamicParameterValidator{}},
260+
expected: []function.DynamicParameterValidator{},
261+
},
262+
"Validators": {
263+
parameter: function.DynamicParameter{
264+
Validators: []function.DynamicParameterValidator{
265+
testvalidator.Dynamic{},
266+
}},
267+
expected: []function.DynamicParameterValidator{
268+
testvalidator.Dynamic{},
269+
},
270+
},
271+
}
272+
273+
for name, testCase := range testCases {
274+
name, testCase := name, testCase
275+
276+
t.Run(name, func(t *testing.T) {
277+
t.Parallel()
278+
279+
got := testCase.parameter.GetValidators()
280+
281+
if diff := cmp.Diff(got, testCase.expected); diff != "" {
282+
t.Errorf("unexpected difference: %s", diff)
283+
}
284+
})
285+
}
286+
}
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package function
5+
6+
import (
7+
"context"
8+
9+
"github.com/hashicorp/terraform-plugin-framework/types"
10+
)
11+
12+
// DynamicParameterValidator is a function validator for types.Dynamic parameters.
13+
type DynamicParameterValidator interface {
14+
15+
// ValidateParameterDynamic performs the validation.
16+
ValidateParameterDynamic(context.Context, DynamicParameterValidatorRequest, *DynamicParameterValidatorResponse)
17+
}
18+
19+
// DynamicParameterValidatorRequest is a request for types.Dynamic schema validation.
20+
type DynamicParameterValidatorRequest struct {
21+
// ArgumentPosition contains the position of the argument for validation.
22+
// Use this position for any response diagnostics.
23+
ArgumentPosition int64
24+
25+
// Value contains the value of the argument for validation.
26+
Value types.Dynamic
27+
}
28+
29+
// DynamicParameterValidatorResponse is a response to a DynamicParameterValidatorRequest.
30+
type DynamicParameterValidatorResponse struct {
31+
// Error is a function error generated during validation of the Value.
32+
Error *FuncError
33+
}

function/float64_parameter.go

+10
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
// Ensure the implementation satisifies the desired interfaces.
1212
var _ Parameter = Float64Parameter{}
13+
var _ ParameterWithFloat64Validators = Float64Parameter{}
1314

1415
// Float64Parameter represents a function parameter that is a 64-bit floating
1516
// point number.
@@ -67,6 +68,15 @@ type Float64Parameter struct {
6768
// alphabetical character and followed by alphanumeric or underscore
6869
// characters.
6970
Name string
71+
72+
// Validators is a list of float64 validators that should be applied to the
73+
// parameter.
74+
Validators []Float64ParameterValidator
75+
}
76+
77+
// GetValidators returns the list of validators for the parameter.
78+
func (p Float64Parameter) GetValidators() []Float64ParameterValidator {
79+
return p.Validators
7080
}
7181

7282
// GetAllowNullValue returns if the parameter accepts a null value.

function/float64_parameter_test.go

+44
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import (
77
"testing"
88

99
"github.com/google/go-cmp/cmp"
10+
1011
"github.com/hashicorp/terraform-plugin-framework/attr"
1112
"github.com/hashicorp/terraform-plugin-framework/function"
1213
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
14+
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testvalidator"
1315
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
1416
)
1517

@@ -240,3 +242,45 @@ func TestFloat64ParameterGetType(t *testing.T) {
240242
})
241243
}
242244
}
245+
246+
func TestFloat64ParameterFloat64Validators(t *testing.T) {
247+
t.Parallel()
248+
249+
testCases := map[string]struct {
250+
parameter function.Float64Parameter
251+
expected []function.Float64ParameterValidator
252+
}{
253+
"unset": {
254+
parameter: function.Float64Parameter{},
255+
expected: nil,
256+
},
257+
"Validators - empty": {
258+
parameter: function.Float64Parameter{
259+
Validators: []function.Float64ParameterValidator{}},
260+
expected: []function.Float64ParameterValidator{},
261+
},
262+
"Validators": {
263+
parameter: function.Float64Parameter{
264+
Validators: []function.Float64ParameterValidator{
265+
testvalidator.Float64{},
266+
}},
267+
expected: []function.Float64ParameterValidator{
268+
testvalidator.Float64{},
269+
},
270+
},
271+
}
272+
273+
for name, testCase := range testCases {
274+
name, testCase := name, testCase
275+
276+
t.Run(name, func(t *testing.T) {
277+
t.Parallel()
278+
279+
got := testCase.parameter.GetValidators()
280+
281+
if diff := cmp.Diff(got, testCase.expected); diff != "" {
282+
t.Errorf("unexpected difference: %s", diff)
283+
}
284+
})
285+
}
286+
}
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package function
5+
6+
import (
7+
"context"
8+
9+
"github.com/hashicorp/terraform-plugin-framework/types"
10+
)
11+
12+
// Float64ParameterValidator is a function validator for types.Float64 parameters.
13+
type Float64ParameterValidator interface {
14+
15+
// ValidateParameterFloat64 performs the validation.
16+
ValidateParameterFloat64(context.Context, Float64ParameterValidatorRequest, *Float64ParameterValidatorResponse)
17+
}
18+
19+
// Float64ParameterValidatorRequest is a request for types.Float64 schema validation.
20+
type Float64ParameterValidatorRequest struct {
21+
// ArgumentPosition contains the position of the argument for validation.
22+
// Use this position for any response diagnostics.
23+
ArgumentPosition int64
24+
25+
// Value contains the value of the argument for validation.
26+
Value types.Float64
27+
}
28+
29+
// Float64ParameterValidatorResponse is a response to a Float64ParameterValidatorRequest.
30+
type Float64ParameterValidatorResponse struct {
31+
// Error is a function error generated during validation of the Value.
32+
Error *FuncError
33+
}

function/int64_parameter.go

+10
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
// Ensure the implementation satisifies the desired interfaces.
1212
var _ Parameter = Int64Parameter{}
13+
var _ ParameterWithInt64Validators = Int64Parameter{}
1314

1415
// Int64Parameter represents a function parameter that is a 64-bit integer.
1516
//
@@ -66,6 +67,15 @@ type Int64Parameter struct {
6667
// alphabetical character and followed by alphanumeric or underscore
6768
// characters.
6869
Name string
70+
71+
// Validators is a list of int64 validators that should be applied to the
72+
// parameter.
73+
Validators []Int64ParameterValidator
74+
}
75+
76+
// GetValidators returns the list of validators for the parameter.
77+
func (p Int64Parameter) GetValidators() []Int64ParameterValidator {
78+
return p.Validators
6979
}
7080

7181
// GetAllowNullValue returns if the parameter accepts a null value.

0 commit comments

Comments
 (0)