Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add type-based provider-defined function parameter validation #968

Merged
merged 43 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
5930e21
Add ValidateableAttribute and ValidateableParameter interfaces
bendbennett Mar 19, 2024
e3b5956
Deprecate xattr.TypeWithValidate
bendbennett Mar 19, 2024
a285776
Merge remote-tracking branch 'origin/main' into bendbennett/pdf-param…
bendbennett Mar 21, 2024
30cc1d9
Modify function signature of ArgumentsData() to return a function.Fun…
bendbennett Mar 21, 2024
b87d7ee
Modify Data.SetAtPath to use switch statement to handle validation.Va…
bendbennett Mar 21, 2024
cc06028
Modify Data.SetAtPathTransformFunc to use switch statement to handle …
bendbennett Mar 21, 2024
cf60c85
Temporarily moving ValidateableAttribute interface to xattr package t…
bendbennett Mar 22, 2024
995b96c
Adding test coverage for list, map and set for SetAtPathTransformFunc
bendbennett Mar 22, 2024
fe30c12
Adding test coverage for bool ValidateableParameter for fromproto5/6 …
bendbennett Mar 22, 2024
7328488
Adding switch statement for ValidateableAttribute to fwschemadata Dat…
bendbennett Mar 22, 2024
2437643
Merge remote-tracking branch 'origin/main' into bendbennett/pdf-param…
bendbennett Mar 22, 2024
b27c7fa
Adding switch statements to internal/reflect/interfaces to handle Val…
bendbennett Mar 22, 2024
a8103f6
Fix usage of incorrect test type
bendbennett Mar 22, 2024
06a5b87
Adding switch statements to internal/reflect/primitive to handle Vali…
bendbennett Mar 22, 2024
f410606
Renaming interfaces
bendbennett Mar 22, 2024
bab78d4
Add Equal() methods to ListValueWithValidateAttributeWarning and MapV…
bendbennett Apr 2, 2024
50c673a
Adding switch statements to reflect package FromMap() function to han…
bendbennett Apr 2, 2024
e0017b0
Adding switch statements to reflect package FromInt(), FromUint(), Fr…
bendbennett Apr 2, 2024
1d2c768
Adding switch statements to reflect package FromPointer() function to…
bendbennett Apr 2, 2024
12dcc5c
Adding switch statements to reflect package FromSlice() function to h…
bendbennett Apr 2, 2024
ce4b934
Adding switch statements to reflect package FromStruct() function to …
bendbennett Apr 2, 2024
bb76e62
Merge remote-tracking branch 'origin/main' into bendbennett/pdf-param…
bendbennett Apr 3, 2024
d04737c
fromproto5+fromproto6: Add further test coverage for validation of fu…
bendbennett Apr 3, 2024
b6d8b93
xfwfunction: Moving Definition.Parameter() method from function packa…
bendbennett Apr 3, 2024
3e0bc53
fromproto5+fromproto6: Fix function error message in ArgumentsData()
bendbennett Apr 3, 2024
6de0382
website: Add documentation for usage of xattr.ValidateableAttribute a…
bendbennett Apr 4, 2024
c8b530b
Merge remote-tracking branch 'origin/main' into bendbennett/pdf-param…
bendbennett Apr 4, 2024
ebea449
website: Add documentation for usage of xattr.ValidateableAttribute a…
bendbennett Apr 4, 2024
399acd1
Adding changelog entries
bendbennett Apr 4, 2024
6a32373
function: Remove unused Definition.Parameter() method
bendbennett Apr 5, 2024
c758c9c
attr/xattr: Remove TODO
bendbennett Apr 5, 2024
fb75b19
attr/xattr: Modify deprecation comment
bendbennett Apr 5, 2024
6f49065
fwschemadata: Reordering logging calls
bendbennett Apr 5, 2024
e26cde9
fromproto5+fromproto6: Inline logic from Parameter() function in Argu…
bendbennett Apr 9, 2024
24db3f2
Remove value type-specific interfaces for <Type>ValuableWithValidatea…
bendbennett Apr 9, 2024
6283788
website: Adding documentation for parameter validation into parameter…
bendbennett Apr 9, 2024
83293bd
function: Moving ValidateableParameter interface to function package
bendbennett Apr 9, 2024
75e30e5
Amend docs
bendbennett Apr 9, 2024
6c517b1
Apply suggestions from code review
bendbennett Apr 9, 2024
c55b74b
fromproto5: Remove unused function
bendbennett Apr 9, 2024
0d3ff07
fromproto5+fromproto6: Remove unneeded case statements
bendbennett Apr 9, 2024
cf398ce
fromproto5: Removing errant test case
bendbennett Apr 9, 2024
2d27299
attr/xattr: Modifying Validateable Go doc comment to highlight the im…
bendbennett Apr 11, 2024
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
37 changes: 37 additions & 0 deletions attr/xattr/attribute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

// TODO: Just putting here for the moment to get rid of import cycle.
bendbennett marked this conversation as resolved.
Show resolved Hide resolved
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.
type ValidateableAttribute interface {
bendbennett marked this conversation as resolved.
Show resolved Hide resolved
// 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
}
6 changes: 5 additions & 1 deletion attr/xattr/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ 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 value type-specific AttributeWithValidate and
// ParameterWithValidate interfaces in the validation package instead.
type TypeWithValidate interface {
attr.Type

Expand Down
149 changes: 92 additions & 57 deletions internal/fromproto5/arguments_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,41 @@ import (
"context"
"fmt"

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

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/attr/xattr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-framework/types/validation"
)

// ArgumentsData returns the ArgumentsData for a given []*tfprotov5.DynamicValue
// and function.Definition.
func ArgumentsData(ctx context.Context, arguments []*tfprotov5.DynamicValue, definition function.Definition) (function.ArgumentsData, diag.Diagnostics) {
func ArgumentsData(ctx context.Context, arguments []*tfprotov5.DynamicValue, definition function.Definition) (function.ArgumentsData, *function.FuncError) {
if definition.VariadicParameter == nil && len(arguments) != len(definition.Parameters) {
return function.NewArgumentsData(nil), diag.Diagnostics{
diag.NewErrorDiagnostic(
"Unexpected Function Arguments Data",
"The provider received an unexpected number of function arguments from Terraform for the given function definition. "+
"This is always an issue in terraform-plugin-framework or Terraform itself and should be reported to the provider developers.\n\n"+
fmt.Sprintf("Expected function arguments: %d\n", len(definition.Parameters))+
fmt.Sprintf("Given function arguments: %d", len(arguments)),
),
}
return function.NewArgumentsData(nil), function.NewFuncError(
"Unexpected Function Arguments Data: " +
"The provider received an unexpected number of function arguments from Terraform for the given function definition. " +
"This is always an issue in terraform-plugin-framework or Terraform itself and should be reported to the provider developers.\n\n" +
fmt.Sprintf("Expected function arguments: %d\n", len(definition.Parameters)) +
fmt.Sprintf("Given function arguments: %d", len(arguments)),
)
}

// Expect at least all parameters to have corresponding arguments. Variadic
// parameter might have 0 to n arguments, which is why it is not checked in
// this case.
if len(arguments) < len(definition.Parameters) {
return function.NewArgumentsData(nil), diag.Diagnostics{
diag.NewErrorDiagnostic(
"Unexpected Function Arguments Data",
"The provider received an unexpected number of function arguments from Terraform for the given function definition. "+
"This is always an issue in terraform-plugin-framework or Terraform itself and should be reported to the provider developers.\n\n"+
fmt.Sprintf("Expected minimum function arguments: %d\n", len(definition.Parameters))+
fmt.Sprintf("Given function arguments: %d", len(arguments)),
),
}
return function.NewArgumentsData(nil), function.NewFuncError(
"Unexpected Function Arguments Data: " +
"The provider received an unexpected number of function arguments from Terraform for the given function definition. " +
"This is always an issue in terraform-plugin-framework or Terraform itself and should be reported to the provider developers.\n\n" +
fmt.Sprintf("Expected minimum function arguments: %d\n", len(definition.Parameters)) +
fmt.Sprintf("Given function arguments: %d", len(arguments)),
)
}

if definition.VariadicParameter == nil && len(arguments) == 0 {
Expand All @@ -54,73 +51,115 @@ func ArgumentsData(ctx context.Context, arguments []*tfprotov5.DynamicValue, def
// Variadic values are collected as a separate tuple to ease developer usage.
argumentValues := make([]attr.Value, 0, len(definition.Parameters))
variadicValues := make([]attr.Value, 0, len(arguments)-len(definition.Parameters))
var diags diag.Diagnostics
var funcError *function.FuncError

for position, argument := range arguments {
parameter, parameterDiags := definition.Parameter(ctx, position)

diags.Append(parameterDiags...)
funcError = function.ConcatFuncErrors(funcError, function.FuncErrorFromDiags(ctx, parameterDiags))

if diags.HasError() {
return function.NewArgumentsData(nil), diags
if funcError != nil {
return function.NewArgumentsData(nil), funcError
}

parameterType := parameter.GetType()

pos := int64(position)

if parameterType == nil {
diags.AddError(
"Unable to Convert Function Argument",
"An unexpected error was encountered when converting the function argument from the protocol type. "+
funcError = function.ConcatFuncErrors(funcError, function.NewArgumentFuncError(
pos,
"Unable to Convert Function Argument: "+
"An unexpected error was encountered when converting the function argument from the protocol type. "+
"This is always an issue in terraform-plugin-framework used to implement the provider and should be reported to the provider developers.\n\n"+
"Please report this to the provider developer:\n\n"+
fmt.Sprintf("Parameter type missing at position %d", position),
)
))

return function.NewArgumentsData(nil), diags
return function.NewArgumentsData(nil), funcError
}

tfValue, err := argument.Unmarshal(parameterType.TerraformType(ctx))

if err != nil {
diags.AddError(
"Unable to Convert Function Argument",
"An unexpected error was encountered when converting the function argument from the protocol type. "+
funcError = function.ConcatFuncErrors(funcError, function.NewArgumentFuncError(
pos,
"Unable to Convert Function Argument: "+
"An unexpected error was encountered when converting the function argument from the protocol type. "+
"This is always an issue in terraform-plugin-framework used to implement the provider and should be reported to the provider developers.\n\n"+
"Please report this to the provider developer:\n\n"+
fmt.Sprintf("Unable to unmarshal DynamicValue at position %d: %s", position, err),
)
))

return function.NewArgumentsData(nil), diags
return function.NewArgumentsData(nil), funcError
}

attrValue, err := parameterType.ValueFromTerraform(ctx, tfValue)

if err != nil {
diags.AddError(
"Unable to Convert Function Argument",
"An unexpected error was encountered when converting the function argument from the protocol type. "+
funcError = function.ConcatFuncErrors(funcError, function.NewArgumentFuncError(
pos,
"Unable to Convert Function Argument"+
"An unexpected error was encountered when converting the function argument from the protocol type. "+
"Please report this to the provider developer:\n\n"+
fmt.Sprintf("Unable to convert tftypes to framework type at position %d: %s", position, err),
)
fmt.Sprintf("Unable to convert tftypes to framework type at position %d: %stringVal", position, err),
))

return function.NewArgumentsData(nil), diags
return function.NewArgumentsData(nil), funcError
}

// This is intentionally below the attr.Value conversion so it can be
// updated for any new type system validation interfaces. Note that the
// This is intentionally below the conversion of tftypes.Value to attr.Value
// so it can be updated for any new type system validation interfaces. Note that the
// original xattr.TypeWithValidation interface must set a path.Path,
// which will always be incorrect in the context of functions.
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/589
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/893
if attrTypeWithValidate, ok := parameterType.(xattr.TypeWithValidate); ok {
logging.FrameworkTrace(ctx, "Parameter type implements TypeWithValidate")
logging.FrameworkTrace(ctx, "Calling provider defined Type Validate")
diags.Append(attrTypeWithValidate.Validate(ctx, tfValue, path.Empty())...)
logging.FrameworkTrace(ctx, "Called provider defined Type Validate")
switch t := attrValue.(type) {
case validation.ValidateableParameter:
resp := validation.ValidateParameterResponse{}

logging.FrameworkTrace(ctx, "Parameter value implements ValidateableParameter")
logging.FrameworkTrace(ctx, "Calling provider defined Value ValidateParameter")

t.ValidateParameter(ctx,
validation.ValidateParameterRequest{
Position: pos,
},
&resp,
)

logging.FrameworkTrace(ctx, "Called provider defined Value ValidateParameter")

if resp.Error != nil {
funcError = function.ConcatFuncErrors(funcError, function.NewArgumentFuncError(
pos,
resp.Error.Error(),
))

if diags.HasError() {
continue
}
default:
//nolint:staticcheck // xattr.TypeWithValidate is deprecated, but we still need to support it.
if t, ok := parameterType.(xattr.TypeWithValidate); ok {
logging.FrameworkTrace(ctx, "Parameter type implements TypeWithValidate")
logging.FrameworkTrace(ctx, "Calling provider defined Type Validate")

diags := t.Validate(ctx, tfValue, path.Empty())

logging.FrameworkTrace(ctx, "Called provider defined Type Validate")

if diags.HasError() {
funcErrFromDiags := function.FuncErrorFromDiags(ctx, diags)

if funcErrFromDiags != nil {
funcError = function.ConcatFuncErrors(funcError, function.NewArgumentFuncError(
pos,
funcErrFromDiags.Error()))
}

continue
}
}
}

if definition.VariadicParameter != nil && position >= len(definition.Parameters) {
Expand Down Expand Up @@ -155,18 +194,14 @@ func ArgumentsData(ctx context.Context, arguments []*tfprotov5.DynamicValue, def

variadicValue, variadicValueDiags := basetypes.NewTupleValue(tupleTypes, tupleValues)

diags.Append(variadicValueDiags...)
funcError = function.ConcatFuncErrors(funcError, function.FuncErrorFromDiags(ctx, variadicValueDiags))

if diags.HasError() {
return function.NewArgumentsData(argumentValues), diags
if funcError != nil {
return function.NewArgumentsData(argumentValues), funcError
}

argumentValues = append(argumentValues, variadicValue)
}

if diags.HasError() {
return function.NewArgumentsData(nil), diags
}

return function.NewArgumentsData(argumentValues), diags
return function.NewArgumentsData(argumentValues), funcError
}
Loading
Loading