Skip to content

Commit

Permalink
Add type-based provider-defined function parameter validation (#968)
Browse files Browse the repository at this point in the history
* Add ValidateableAttribute and ValidateableParameter interfaces

* Deprecate xattr.TypeWithValidate

* Modify function signature of ArgumentsData() to return a function.FuncError and add switch to handle validation.ValidateableParameter interface

* Modify Data.SetAtPath to use switch statement to handle validation.ValidateableParameter interface

* Modify Data.SetAtPathTransformFunc to use switch statement to handle validation.ValidateableParameter interface

* Temporarily moving ValidateableAttribute interface to xattr package to avoid import cycle

* Adding test coverage for list, map and set for SetAtPathTransformFunc

* Adding test coverage for bool ValidateableParameter for fromproto5/6 ArgumentsData()

* Adding switch statement for ValidateableAttribute to fwschemadata Data.ValueAtPaath()

* Adding switch statements to internal/reflect/interfaces to handle ValidateableAttribute assertions

* Fix usage of incorrect test type

* Adding switch statements to internal/reflect/primitive to handle ValidateableAttribute assertions

* Renaming interfaces

* Add Equal() methods to ListValueWithValidateAttributeWarning and MapValueWithValidateAttributeWarning

* Adding switch statements to reflect package FromMap() function to handle ValidateableAttribute assertions

* Adding switch statements to reflect package FromInt(), FromUint(), FromFloat(), FromBigFloat(), and FromBigInt() functions to handle ValidateableAttribute assertions

* Adding switch statements to reflect package FromPointer() function to handle ValidateableAttribute assertions

* Adding switch statements to reflect package FromSlice() function to handle ValidateableAttribute assertions

* Adding switch statements to reflect package FromStruct() function to handle ValidateableAttribute assertions

* fromproto5+fromproto6: Add further test coverage for validation of function parameters in ArgumentsData() function

* xfwfunction: Moving Definition.Parameter() method from function package to xfwfunction package Parameter() function

  * xfwfunction package is purely to avoid import cycles

* fromproto5+fromproto6: Fix function error message in ArgumentsData()

* website: Add documentation for usage of xattr.ValidateableAttribute and validation.ValidateableParameter

* website: Add documentation for usage of xattr.ValidateableAttribute and validation.ValidateableParameter

* Adding changelog entries

* function: Remove unused Definition.Parameter() method

* attr/xattr: Remove TODO

* attr/xattr: Modify deprecation comment

* fwschemadata: Reordering logging calls

* fromproto5+fromproto6: Inline logic from Parameter() function in ArgumentsData() function

* Remove value type-specific interfaces for <Type>ValuableWithValidateableAttribute and <Type>ValuableWithValidateableParameter

* website: Adding documentation for parameter validation into parameters page

* function: Moving ValidateableParameter interface to function package

* Amend docs

* Apply suggestions from code review

Co-authored-by: Austin Valle <austinvalle@gmail.com>

* fromproto5: Remove unused function

* fromproto5+fromproto6: Remove unneeded case statements

* fromproto5: Removing errant test case

* attr/xattr: Modifying Validateable Go doc comment to highlight the implicit calling of ValidateAttribute

---------

Co-authored-by: Austin Valle <austinvalle@gmail.com>
  • Loading branch information
bendbennett and austinvalle authored Apr 12, 2024
1 parent 541a7cb commit 4a9b6a3
Show file tree
Hide file tree
Showing 66 changed files with 4,557 additions and 615 deletions.
5 changes: 5 additions & 0 deletions .changes/unreleased/BREAKING CHANGES-20240404-182004.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: BREAKING CHANGES
body: 'function: Removed `Definition` type `Parameter()` method'
time: 2024-04-04T18:20:04.534677+01:00
custom:
Issue: "968"
5 changes: 5 additions & 0 deletions .changes/unreleased/FEATURES-20240404-154339.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: FEATURES
body: 'attr/xattr: Added `ValidateableAttribute` interface for custom value type implementations'
time: 2024-04-04T15:43:39.796606+01:00
custom:
Issue: "968"
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20240404-154439.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'function: Added `ValidateableParameter` interface for custom value
type implementations'
time: 2024-04-04T15:44:39.289946+01:00
custom:
Issue: "968"
7 changes: 7 additions & 0 deletions .changes/unreleased/NOTES-20240404-155606.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
kind: NOTES
body: 'attr/xattr: The `TypeWithValidate` interface has been deprecated in preference
of the `ValidateableAttribute` interface. A `ValidatableParameter` interface has
also been added to the `function` package'
time: 2024-04-04T15:56:06.494328+01:00
custom:
Issue: "968"
38 changes: 38 additions & 0 deletions attr/xattr/attribute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package xattr

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
)

// ValidateableAttribute defines an interface for validating an attribute value.
// The ValidateAttribute method is called implicitly by the framework when value
// types from Terraform are converted into framework types.
type ValidateableAttribute interface {
// ValidateAttribute returns any warnings or errors generated during validation
// of the attribute. It is generally used to check the data format and ensure
// that it complies with the requirements of the Value.
ValidateAttribute(context.Context, ValidateAttributeRequest, *ValidateAttributeResponse)
}

// ValidateAttributeRequest represents a request for the Value to call its
// validation logic. An instance of this request struct is supplied as an
// argument to the ValidateAttribute method.
type ValidateAttributeRequest struct {
// Path is the path to the attribute being validated.
Path path.Path
}

// ValidateAttributeResponse represents a response to a ValidateAttributeRequest.
// An instance of this response struct is supplied as an argument to the
// ValidateAttribute method.
type ValidateAttributeResponse struct {
// Diagnostics is a collection of warnings or errors generated during
// validation of the Value.
Diagnostics diag.Diagnostics
}
7 changes: 6 additions & 1 deletion attr/xattr/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ package xattr
import (
"context"

"github.com/hashicorp/terraform-plugin-go/tftypes"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

// TypeWithValidate extends the attr.Type interface to include a Validate
// method, used to bundle consistent validation logic with the Type.
//
// Deprecated: Use the ValidateableAttribute interface instead for schema
// attribute validation. Use the function.ValidateableParameter interface
// for provider-defined function parameter validation.
type TypeWithValidate interface {
attr.Type

Expand Down
35 changes: 0 additions & 35 deletions function/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,41 +51,6 @@ type Definition struct {
DeprecationMessage string
}

// Parameter returns the Parameter for a given argument position. This may be
// from the Parameters field or, if defined, the VariadicParameter field. An
// error diagnostic is raised if the position is outside the expected arguments.
func (d Definition) Parameter(ctx context.Context, position int) (Parameter, diag.Diagnostics) {
if d.VariadicParameter != nil && position >= len(d.Parameters) {
return d.VariadicParameter, nil
}

if len(d.Parameters) == 0 {
return nil, diag.Diagnostics{
diag.NewErrorDiagnostic(
"Invalid Parameter Position for Definition",
"When determining the parameter for the given argument position, an invalid value was given. "+
"This is always an issue in the provider code and should be reported to the provider developers.\n\n"+
"Function does not implement parameters.\n"+
fmt.Sprintf("Given position: %d", position),
),
}
}

if position >= len(d.Parameters) {
return nil, diag.Diagnostics{
diag.NewErrorDiagnostic(
"Invalid Parameter Position for Definition",
"When determining the parameter for the given argument position, an invalid value was given. "+
"This is always an issue in the provider code and should be reported to the provider developers.\n\n"+
fmt.Sprintf("Max argument position: %d\n", len(d.Parameters)-1)+
fmt.Sprintf("Given position: %d", position),
),
}
}

return d.Parameters[position], nil
}

// ValidateImplementation contains logic for validating the provider-defined
// implementation of the definition to prevent unexpected errors or panics. This
// logic runs during the GetProviderSchema RPC, or via provider-defined unit
Expand Down
133 changes: 0 additions & 133 deletions function/definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,139 +14,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
)

func TestDefinitionParameter(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
definition function.Definition
position int
expected function.Parameter
expectedDiagnostics diag.Diagnostics
}{
"none": {
definition: function.Definition{
// no Parameters or VariadicParameter
},
position: 0,
expected: nil,
expectedDiagnostics: diag.Diagnostics{
diag.NewErrorDiagnostic(
"Invalid Parameter Position for Definition",
"When determining the parameter for the given argument position, an invalid value was given. "+
"This is always an issue in the provider code and should be reported to the provider developers.\n\n"+
"Function does not implement parameters.\n"+
"Given position: 0",
),
},
},
"parameters-first": {
definition: function.Definition{
Parameters: []function.Parameter{
function.BoolParameter{},
function.Int64Parameter{},
function.StringParameter{},
},
},
position: 0,
expected: function.BoolParameter{},
},
"parameters-last": {
definition: function.Definition{
Parameters: []function.Parameter{
function.BoolParameter{},
function.Int64Parameter{},
function.StringParameter{},
},
},
position: 2,
expected: function.StringParameter{},
},
"parameters-middle": {
definition: function.Definition{
Parameters: []function.Parameter{
function.BoolParameter{},
function.Int64Parameter{},
function.StringParameter{},
},
},
position: 1,
expected: function.Int64Parameter{},
},
"parameters-only": {
definition: function.Definition{
Parameters: []function.Parameter{
function.BoolParameter{},
},
},
position: 0,
expected: function.BoolParameter{},
},
"parameters-over": {
definition: function.Definition{
Parameters: []function.Parameter{
function.BoolParameter{},
},
},
position: 1,
expected: nil,
expectedDiagnostics: diag.Diagnostics{
diag.NewErrorDiagnostic(
"Invalid Parameter Position for Definition",
"When determining the parameter for the given argument position, an invalid value was given. "+
"This is always an issue in the provider code and should be reported to the provider developers.\n\n"+
"Max argument position: 0\n"+
"Given position: 1",
),
},
},
"variadicparameter-and-parameters-select-parameter": {
definition: function.Definition{
Parameters: []function.Parameter{
function.BoolParameter{},
},
VariadicParameter: function.StringParameter{},
},
position: 0,
expected: function.BoolParameter{},
},
"variadicparameter-and-parameters-select-variadicparameter": {
definition: function.Definition{
Parameters: []function.Parameter{
function.BoolParameter{},
},
VariadicParameter: function.StringParameter{},
},
position: 1,
expected: function.StringParameter{},
},
"variadicparameter-only": {
definition: function.Definition{
VariadicParameter: function.StringParameter{},
},
position: 0,
expected: function.StringParameter{},
},
}

for name, testCase := range testCases {
name, testCase := name, testCase

t.Run(name, func(t *testing.T) {
t.Parallel()

got, diags := testCase.definition.Parameter(context.Background(), testCase.position)

if diff := cmp.Diff(got, testCase.expected); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}

if diff := cmp.Diff(diags, testCase.expectedDiagnostics); diff != "" {
t.Errorf("unexpected diagnostics difference: %s", diff)
}
})
}
}

func TestDefinitionValidateImplementation(t *testing.T) {
t.Parallel()

Expand Down
26 changes: 26 additions & 0 deletions function/parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
package function

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/attr"
)

Expand Down Expand Up @@ -38,3 +40,27 @@ type Parameter interface {
// Function type Run method.
GetType() attr.Type
}

// ValidateableParameter defines an interface for validating a parameter value.
type ValidateableParameter interface {
// ValidateParameter returns any error generated during validation
// of the parameter. It is generally used to check the data format and ensure
// that it complies with the requirements of the attr.Value.
ValidateParameter(context.Context, ValidateParameterRequest, *ValidateParameterResponse)
}

// ValidateParameterRequest represents a request for the attr.Value to call its
// validation logic. An instance of this request struct is supplied as an
// argument to the attr.Value type ValidateParameter method.
type ValidateParameterRequest struct {
// Position is the zero-ordered position of the parameter being validated.
Position int64
}

// ValidateParameterResponse represents a response to a ValidateParameterRequest.
// An instance of this response struct is supplied as an argument to the
// ValidateParameter method.
type ValidateParameterResponse struct {
// Error is a function error generated during validation of the attr.Value.
Error *FuncError
}
Loading

0 comments on commit 4a9b6a3

Please sign in to comment.