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

function: Missing underlying type validation and refactor parameter name validation #991

Merged
merged 6 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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-20240423-165354.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: ENHANCEMENTS
body: 'function: Introduced implementation errors for collection and object parameters
and returns which are missing type information'
time: 2024-04-23T16:53:54.509459-04:00
custom:
Issue: "991"
10 changes: 10 additions & 0 deletions function/bool_parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
package function

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwfunction"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)

// Ensure the implementation satisifies the desired interfaces.
var _ Parameter = BoolParameter{}
var _ ParameterWithBoolValidators = BoolParameter{}
var _ fwfunction.ParameterWithValidateImplementation = BoolParameter{}

// BoolParameter represents a function parameter that is a boolean.
//
Expand Down Expand Up @@ -115,3 +119,9 @@ func (p BoolParameter) GetType() attr.Type {

return basetypes.BoolType{}
}

func (p BoolParameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) {
if p.GetName() == "" {
resp.Diagnostics.Append(fwfunction.MissingParameterNameDiag(req.FunctionName, req.ParameterPosition))
}
}
58 changes: 58 additions & 0 deletions function/bool_parameter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
package function_test

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/internal/fwfunction"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testvalidator"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
Expand Down Expand Up @@ -284,3 +287,58 @@ func TestBoolParameterBoolValidators(t *testing.T) {
})
}
}

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

testCases := map[string]struct {
param function.BoolParameter
request fwfunction.ValidateParameterImplementationRequest
expected *fwfunction.ValidateParameterImplementationResponse
}{
"name": {
param: function.BoolParameter{
Name: "testparam",
},
request: fwfunction.ValidateParameterImplementationRequest{
FunctionName: "testfunc",
ParameterPosition: pointer(int64(0)),
},
expected: &fwfunction.ValidateParameterImplementationResponse{},
},
"name-missing": {
param: function.BoolParameter{
// Name intentionally missing
},
request: fwfunction.ValidateParameterImplementationRequest{
FunctionName: "testfunc",
ParameterPosition: pointer(int64(0)),
},
expected: &fwfunction.ValidateParameterImplementationResponse{
Diagnostics: diag.Diagnostics{
diag.NewErrorDiagnostic(
"Invalid Function Definition",
"When validating the function definition, an implementation issue was found. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
"Function \"testfunc\" - Parameter at position 0 does not have a name",
),
},
},
},
}

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

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

got := &fwfunction.ValidateParameterImplementationResponse{}
testCase.param.ValidateImplementation(context.Background(), testCase.request, got)

if diff := cmp.Diff(got, testCase.expected); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}
})
}
}
29 changes: 6 additions & 23 deletions function/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,10 @@ func (d Definition) ValidateImplementation(ctx context.Context, req DefinitionVa
paramNames := make(map[string]int, len(d.Parameters))
for pos, param := range d.Parameters {
parameterPosition := int64(pos)
name := param.GetName()
// If name is not set, add an error diagnostic, parameter names are mandatory.
if name == "" {
diags.AddError(
"Invalid Function Definition",
"When validating the function definition, an implementation issue was found. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("Function %q - Parameter at position %d does not have a name", req.FuncName, pos),
)
}

if paramWithValidateImplementation, ok := param.(fwfunction.ParameterWithValidateImplementation); ok {
req := fwfunction.ValidateParameterImplementationRequest{
Name: name,
FunctionName: req.FuncName,
ParameterPosition: &parameterPosition,
}
resp := &fwfunction.ValidateParameterImplementationResponse{}
Expand All @@ -104,7 +94,9 @@ func (d Definition) ValidateImplementation(ctx context.Context, req DefinitionVa
diags.Append(resp.Diagnostics...)
}

name := param.GetName()
conflictPos, exists := paramNames[name]

if exists && name != "" {
diags.AddError(
"Invalid Function Definition",
Expand All @@ -120,20 +112,9 @@ func (d Definition) ValidateImplementation(ctx context.Context, req DefinitionVa
}

if d.VariadicParameter != nil {
name := d.VariadicParameter.GetName()
// If name is not set, add an error diagnostic, parameter names are mandatory.
if name == "" {
diags.AddError(
"Invalid Function Definition",
"When validating the function definition, an implementation issue was found. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("Function %q - The variadic parameter does not have a name", req.FuncName),
)
}

if paramWithValidateImplementation, ok := d.VariadicParameter.(fwfunction.ParameterWithValidateImplementation); ok {
req := fwfunction.ValidateParameterImplementationRequest{
Name: name,
FunctionName: req.FuncName,
}
resp := &fwfunction.ValidateParameterImplementationResponse{}

Expand All @@ -142,7 +123,9 @@ func (d Definition) ValidateImplementation(ctx context.Context, req DefinitionVa
diags.Append(resp.Diagnostics...)
}

name := d.VariadicParameter.GetName()
conflictPos, exists := paramNames[name]

if exists && name != "" {
diags.AddError(
"Invalid Function Definition",
Expand Down
10 changes: 10 additions & 0 deletions function/dynamic_parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
package function

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwfunction"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)

// Ensure the implementation satisifies the desired interfaces.
var _ Parameter = DynamicParameter{}
var _ ParameterWithDynamicValidators = DynamicParameter{}
var _ fwfunction.ParameterWithValidateImplementation = DynamicParameter{}

// DynamicParameter represents a function parameter that is a dynamic, rather
// than a static type. Static types are always preferable over dynamic
Expand Down Expand Up @@ -110,3 +114,9 @@ func (p DynamicParameter) GetType() attr.Type {

return basetypes.DynamicType{}
}

func (p DynamicParameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) {
if p.GetName() == "" {
resp.Diagnostics.Append(fwfunction.MissingParameterNameDiag(req.FunctionName, req.ParameterPosition))
}
}
58 changes: 58 additions & 0 deletions function/dynamic_parameter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
package function_test

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/internal/fwfunction"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testvalidator"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
Expand Down Expand Up @@ -284,3 +287,58 @@ func TestDynamicParameterDynamicValidators(t *testing.T) {
})
}
}

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

testCases := map[string]struct {
param function.DynamicParameter
request fwfunction.ValidateParameterImplementationRequest
expected *fwfunction.ValidateParameterImplementationResponse
}{
"name": {
param: function.DynamicParameter{
Name: "testparam",
},
request: fwfunction.ValidateParameterImplementationRequest{
FunctionName: "testfunc",
ParameterPosition: pointer(int64(0)),
},
expected: &fwfunction.ValidateParameterImplementationResponse{},
},
"name-missing": {
param: function.DynamicParameter{
// Name intentionally missing
},
request: fwfunction.ValidateParameterImplementationRequest{
FunctionName: "testfunc",
ParameterPosition: pointer(int64(0)),
},
expected: &fwfunction.ValidateParameterImplementationResponse{
Diagnostics: diag.Diagnostics{
diag.NewErrorDiagnostic(
"Invalid Function Definition",
"When validating the function definition, an implementation issue was found. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
"Function \"testfunc\" - Parameter at position 0 does not have a name",
),
},
},
},
}

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

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

got := &fwfunction.ValidateParameterImplementationResponse{}
testCase.param.ValidateImplementation(context.Background(), testCase.request, got)

if diff := cmp.Diff(got, testCase.expected); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}
})
}
}
10 changes: 10 additions & 0 deletions function/float64_parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
package function

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/internal/fwfunction"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)

// Ensure the implementation satisifies the desired interfaces.
var _ Parameter = Float64Parameter{}
var _ ParameterWithFloat64Validators = Float64Parameter{}
var _ fwfunction.ParameterWithValidateImplementation = Float64Parameter{}

// Float64Parameter represents a function parameter that is a 64-bit floating
// point number.
Expand Down Expand Up @@ -112,3 +116,9 @@ func (p Float64Parameter) GetType() attr.Type {

return basetypes.Float64Type{}
}

func (p Float64Parameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) {
if p.GetName() == "" {
resp.Diagnostics.Append(fwfunction.MissingParameterNameDiag(req.FunctionName, req.ParameterPosition))
}
}
58 changes: 58 additions & 0 deletions function/float64_parameter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
package function_test

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/internal/fwfunction"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testvalidator"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
Expand Down Expand Up @@ -284,3 +287,58 @@ func TestFloat64ParameterFloat64Validators(t *testing.T) {
})
}
}

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

testCases := map[string]struct {
param function.Float64Parameter
request fwfunction.ValidateParameterImplementationRequest
expected *fwfunction.ValidateParameterImplementationResponse
}{
"name": {
param: function.Float64Parameter{
Name: "testparam",
},
request: fwfunction.ValidateParameterImplementationRequest{
FunctionName: "testfunc",
ParameterPosition: pointer(int64(0)),
},
expected: &fwfunction.ValidateParameterImplementationResponse{},
},
"name-missing": {
param: function.Float64Parameter{
// Name intentionally missing
},
request: fwfunction.ValidateParameterImplementationRequest{
FunctionName: "testfunc",
ParameterPosition: pointer(int64(0)),
},
expected: &fwfunction.ValidateParameterImplementationResponse{
Diagnostics: diag.Diagnostics{
diag.NewErrorDiagnostic(
"Invalid Function Definition",
"When validating the function definition, an implementation issue was found. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
"Function \"testfunc\" - Parameter at position 0 does not have a name",
),
},
},
},
}

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

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

got := &fwfunction.ValidateParameterImplementationResponse{}
testCase.param.ValidateImplementation(context.Background(), testCase.request, got)

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