Skip to content

Commit a150eef

Browse files
authored
function: Missing underlying type validation and refactor parameter name validation (#991)
Reference: #965 Reference: #967 The framework will now raise implementation errors if a function parameter or return definition is missing underlying type information (e.g. collection element type or object attribute type). Only framework types are considered in the initial implementation.
1 parent cc4a6c7 commit a150eef

34 files changed

+3660
-86
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
kind: ENHANCEMENTS
2+
body: 'function: Introduced implementation errors for collection and object parameters
3+
and returns which are missing type information'
4+
time: 2024-04-23T16:53:54.509459-04:00
5+
custom:
6+
Issue: "991"

function/bool_parameter.go

+10
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@
44
package function
55

66
import (
7+
"context"
8+
79
"github.com/hashicorp/terraform-plugin-framework/attr"
10+
"github.com/hashicorp/terraform-plugin-framework/internal/fwfunction"
811
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
912
)
1013

1114
// Ensure the implementation satisifies the desired interfaces.
1215
var _ Parameter = BoolParameter{}
1316
var _ ParameterWithBoolValidators = BoolParameter{}
17+
var _ fwfunction.ParameterWithValidateImplementation = BoolParameter{}
1418

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

116120
return basetypes.BoolType{}
117121
}
122+
123+
func (p BoolParameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) {
124+
if p.GetName() == "" {
125+
resp.Diagnostics.Append(fwfunction.MissingParameterNameDiag(req.FunctionName, req.ParameterPosition))
126+
}
127+
}

function/bool_parameter_test.go

+58
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
package function_test
55

66
import (
7+
"context"
78
"testing"
89

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

1112
"github.com/hashicorp/terraform-plugin-framework/attr"
13+
"github.com/hashicorp/terraform-plugin-framework/diag"
1214
"github.com/hashicorp/terraform-plugin-framework/function"
15+
"github.com/hashicorp/terraform-plugin-framework/internal/fwfunction"
1316
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
1417
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testvalidator"
1518
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
@@ -284,3 +287,58 @@ func TestBoolParameterBoolValidators(t *testing.T) {
284287
})
285288
}
286289
}
290+
291+
func TestBoolParameterValidateImplementation(t *testing.T) {
292+
t.Parallel()
293+
294+
testCases := map[string]struct {
295+
param function.BoolParameter
296+
request fwfunction.ValidateParameterImplementationRequest
297+
expected *fwfunction.ValidateParameterImplementationResponse
298+
}{
299+
"name": {
300+
param: function.BoolParameter{
301+
Name: "testparam",
302+
},
303+
request: fwfunction.ValidateParameterImplementationRequest{
304+
FunctionName: "testfunc",
305+
ParameterPosition: pointer(int64(0)),
306+
},
307+
expected: &fwfunction.ValidateParameterImplementationResponse{},
308+
},
309+
"name-missing": {
310+
param: function.BoolParameter{
311+
// Name intentionally missing
312+
},
313+
request: fwfunction.ValidateParameterImplementationRequest{
314+
FunctionName: "testfunc",
315+
ParameterPosition: pointer(int64(0)),
316+
},
317+
expected: &fwfunction.ValidateParameterImplementationResponse{
318+
Diagnostics: diag.Diagnostics{
319+
diag.NewErrorDiagnostic(
320+
"Invalid Function Definition",
321+
"When validating the function definition, an implementation issue was found. "+
322+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
323+
"Function \"testfunc\" - Parameter at position 0 does not have a name",
324+
),
325+
},
326+
},
327+
},
328+
}
329+
330+
for name, testCase := range testCases {
331+
name, testCase := name, testCase
332+
333+
t.Run(name, func(t *testing.T) {
334+
t.Parallel()
335+
336+
got := &fwfunction.ValidateParameterImplementationResponse{}
337+
testCase.param.ValidateImplementation(context.Background(), testCase.request, got)
338+
339+
if diff := cmp.Diff(got, testCase.expected); diff != "" {
340+
t.Errorf("unexpected difference: %s", diff)
341+
}
342+
})
343+
}
344+
}

function/definition.go

+6-23
Original file line numberDiff line numberDiff line change
@@ -81,20 +81,10 @@ func (d Definition) ValidateImplementation(ctx context.Context, req DefinitionVa
8181
paramNames := make(map[string]int, len(d.Parameters))
8282
for pos, param := range d.Parameters {
8383
parameterPosition := int64(pos)
84-
name := param.GetName()
85-
// If name is not set, add an error diagnostic, parameter names are mandatory.
86-
if name == "" {
87-
diags.AddError(
88-
"Invalid Function Definition",
89-
"When validating the function definition, an implementation issue was found. "+
90-
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
91-
fmt.Sprintf("Function %q - Parameter at position %d does not have a name", req.FuncName, pos),
92-
)
93-
}
9484

9585
if paramWithValidateImplementation, ok := param.(fwfunction.ParameterWithValidateImplementation); ok {
9686
req := fwfunction.ValidateParameterImplementationRequest{
97-
Name: name,
87+
FunctionName: req.FuncName,
9888
ParameterPosition: &parameterPosition,
9989
}
10090
resp := &fwfunction.ValidateParameterImplementationResponse{}
@@ -104,7 +94,9 @@ func (d Definition) ValidateImplementation(ctx context.Context, req DefinitionVa
10494
diags.Append(resp.Diagnostics...)
10595
}
10696

97+
name := param.GetName()
10798
conflictPos, exists := paramNames[name]
99+
108100
if exists && name != "" {
109101
diags.AddError(
110102
"Invalid Function Definition",
@@ -120,20 +112,9 @@ func (d Definition) ValidateImplementation(ctx context.Context, req DefinitionVa
120112
}
121113

122114
if d.VariadicParameter != nil {
123-
name := d.VariadicParameter.GetName()
124-
// If name is not set, add an error diagnostic, parameter names are mandatory.
125-
if name == "" {
126-
diags.AddError(
127-
"Invalid Function Definition",
128-
"When validating the function definition, an implementation issue was found. "+
129-
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
130-
fmt.Sprintf("Function %q - The variadic parameter does not have a name", req.FuncName),
131-
)
132-
}
133-
134115
if paramWithValidateImplementation, ok := d.VariadicParameter.(fwfunction.ParameterWithValidateImplementation); ok {
135116
req := fwfunction.ValidateParameterImplementationRequest{
136-
Name: name,
117+
FunctionName: req.FuncName,
137118
}
138119
resp := &fwfunction.ValidateParameterImplementationResponse{}
139120

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

126+
name := d.VariadicParameter.GetName()
145127
conflictPos, exists := paramNames[name]
128+
146129
if exists && name != "" {
147130
diags.AddError(
148131
"Invalid Function Definition",

function/dynamic_parameter.go

+10
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@
44
package function
55

66
import (
7+
"context"
8+
79
"github.com/hashicorp/terraform-plugin-framework/attr"
10+
"github.com/hashicorp/terraform-plugin-framework/internal/fwfunction"
811
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
912
)
1013

1114
// Ensure the implementation satisifies the desired interfaces.
1215
var _ Parameter = DynamicParameter{}
1316
var _ ParameterWithDynamicValidators = DynamicParameter{}
17+
var _ fwfunction.ParameterWithValidateImplementation = DynamicParameter{}
1418

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

111115
return basetypes.DynamicType{}
112116
}
117+
118+
func (p DynamicParameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) {
119+
if p.GetName() == "" {
120+
resp.Diagnostics.Append(fwfunction.MissingParameterNameDiag(req.FunctionName, req.ParameterPosition))
121+
}
122+
}

function/dynamic_parameter_test.go

+58
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
package function_test
55

66
import (
7+
"context"
78
"testing"
89

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

1112
"github.com/hashicorp/terraform-plugin-framework/attr"
13+
"github.com/hashicorp/terraform-plugin-framework/diag"
1214
"github.com/hashicorp/terraform-plugin-framework/function"
15+
"github.com/hashicorp/terraform-plugin-framework/internal/fwfunction"
1316
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
1417
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testvalidator"
1518
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
@@ -284,3 +287,58 @@ func TestDynamicParameterDynamicValidators(t *testing.T) {
284287
})
285288
}
286289
}
290+
291+
func TestDynamicParameterValidateImplementation(t *testing.T) {
292+
t.Parallel()
293+
294+
testCases := map[string]struct {
295+
param function.DynamicParameter
296+
request fwfunction.ValidateParameterImplementationRequest
297+
expected *fwfunction.ValidateParameterImplementationResponse
298+
}{
299+
"name": {
300+
param: function.DynamicParameter{
301+
Name: "testparam",
302+
},
303+
request: fwfunction.ValidateParameterImplementationRequest{
304+
FunctionName: "testfunc",
305+
ParameterPosition: pointer(int64(0)),
306+
},
307+
expected: &fwfunction.ValidateParameterImplementationResponse{},
308+
},
309+
"name-missing": {
310+
param: function.DynamicParameter{
311+
// Name intentionally missing
312+
},
313+
request: fwfunction.ValidateParameterImplementationRequest{
314+
FunctionName: "testfunc",
315+
ParameterPosition: pointer(int64(0)),
316+
},
317+
expected: &fwfunction.ValidateParameterImplementationResponse{
318+
Diagnostics: diag.Diagnostics{
319+
diag.NewErrorDiagnostic(
320+
"Invalid Function Definition",
321+
"When validating the function definition, an implementation issue was found. "+
322+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
323+
"Function \"testfunc\" - Parameter at position 0 does not have a name",
324+
),
325+
},
326+
},
327+
},
328+
}
329+
330+
for name, testCase := range testCases {
331+
name, testCase := name, testCase
332+
333+
t.Run(name, func(t *testing.T) {
334+
t.Parallel()
335+
336+
got := &fwfunction.ValidateParameterImplementationResponse{}
337+
testCase.param.ValidateImplementation(context.Background(), testCase.request, got)
338+
339+
if diff := cmp.Diff(got, testCase.expected); diff != "" {
340+
t.Errorf("unexpected difference: %s", diff)
341+
}
342+
})
343+
}
344+
}

function/float64_parameter.go

+10
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@
44
package function
55

66
import (
7+
"context"
8+
79
"github.com/hashicorp/terraform-plugin-framework/attr"
10+
"github.com/hashicorp/terraform-plugin-framework/internal/fwfunction"
811
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
912
)
1013

1114
// Ensure the implementation satisifies the desired interfaces.
1215
var _ Parameter = Float64Parameter{}
1316
var _ ParameterWithFloat64Validators = Float64Parameter{}
17+
var _ fwfunction.ParameterWithValidateImplementation = Float64Parameter{}
1418

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

113117
return basetypes.Float64Type{}
114118
}
119+
120+
func (p Float64Parameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) {
121+
if p.GetName() == "" {
122+
resp.Diagnostics.Append(fwfunction.MissingParameterNameDiag(req.FunctionName, req.ParameterPosition))
123+
}
124+
}

function/float64_parameter_test.go

+58
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
package function_test
55

66
import (
7+
"context"
78
"testing"
89

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

1112
"github.com/hashicorp/terraform-plugin-framework/attr"
13+
"github.com/hashicorp/terraform-plugin-framework/diag"
1214
"github.com/hashicorp/terraform-plugin-framework/function"
15+
"github.com/hashicorp/terraform-plugin-framework/internal/fwfunction"
1316
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes"
1417
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testvalidator"
1518
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
@@ -284,3 +287,58 @@ func TestFloat64ParameterFloat64Validators(t *testing.T) {
284287
})
285288
}
286289
}
290+
291+
func TestFloat64ParameterValidateImplementation(t *testing.T) {
292+
t.Parallel()
293+
294+
testCases := map[string]struct {
295+
param function.Float64Parameter
296+
request fwfunction.ValidateParameterImplementationRequest
297+
expected *fwfunction.ValidateParameterImplementationResponse
298+
}{
299+
"name": {
300+
param: function.Float64Parameter{
301+
Name: "testparam",
302+
},
303+
request: fwfunction.ValidateParameterImplementationRequest{
304+
FunctionName: "testfunc",
305+
ParameterPosition: pointer(int64(0)),
306+
},
307+
expected: &fwfunction.ValidateParameterImplementationResponse{},
308+
},
309+
"name-missing": {
310+
param: function.Float64Parameter{
311+
// Name intentionally missing
312+
},
313+
request: fwfunction.ValidateParameterImplementationRequest{
314+
FunctionName: "testfunc",
315+
ParameterPosition: pointer(int64(0)),
316+
},
317+
expected: &fwfunction.ValidateParameterImplementationResponse{
318+
Diagnostics: diag.Diagnostics{
319+
diag.NewErrorDiagnostic(
320+
"Invalid Function Definition",
321+
"When validating the function definition, an implementation issue was found. "+
322+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
323+
"Function \"testfunc\" - Parameter at position 0 does not have a name",
324+
),
325+
},
326+
},
327+
},
328+
}
329+
330+
for name, testCase := range testCases {
331+
name, testCase := name, testCase
332+
333+
t.Run(name, func(t *testing.T) {
334+
t.Parallel()
335+
336+
got := &fwfunction.ValidateParameterImplementationResponse{}
337+
testCase.param.ValidateImplementation(context.Background(), testCase.request, got)
338+
339+
if diff := cmp.Diff(got, testCase.expected); diff != "" {
340+
t.Errorf("unexpected difference: %s", diff)
341+
}
342+
})
343+
}
344+
}

0 commit comments

Comments
 (0)