diff --git a/.changes/unreleased/BREAKING CHANGES-20240320-095152.yaml b/.changes/unreleased/BREAKING CHANGES-20240320-095152.yaml new file mode 100644 index 000000000..4e2a25450 --- /dev/null +++ b/.changes/unreleased/BREAKING CHANGES-20240320-095152.yaml @@ -0,0 +1,5 @@ +kind: BREAKING CHANGES +body: 'function: All parameters must be explicitly named via the `Name` field' +time: 2024-03-20T09:51:52.869254Z +custom: + Issue: "964" diff --git a/.changes/unreleased/BREAKING CHANGES-20240320-171454.yaml b/.changes/unreleased/BREAKING CHANGES-20240320-171454.yaml new file mode 100644 index 000000000..0e89a43fb --- /dev/null +++ b/.changes/unreleased/BREAKING CHANGES-20240320-171454.yaml @@ -0,0 +1,6 @@ +kind: BREAKING CHANGES +body: 'function: `DefaultParameterNamePrefix` and `DefaultVariadicParameterName` constants + have been removed' +time: 2024-03-20T17:14:54.480412Z +custom: + Issue: "964" diff --git a/function/definition.go b/function/definition.go index 9ab546b56..87dd45fb2 100644 --- a/function/definition.go +++ b/function/definition.go @@ -90,7 +90,7 @@ func (d Definition) Parameter(ctx context.Context, position int) (Parameter, dia // implementation of the definition to prevent unexpected errors or panics. This // logic runs during the GetProviderSchema RPC, or via provider-defined unit // testing, and should never include false positives. -func (d Definition) ValidateImplementation(ctx context.Context) diag.Diagnostics { +func (d Definition) ValidateImplementation(ctx context.Context, req DefinitionValidateRequest, resp *DefinitionValidateResponse) { var diags diag.Diagnostics if d.Return == nil { @@ -98,14 +98,14 @@ func (d Definition) ValidateImplementation(ctx context.Context) diag.Diagnostics "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"+ - "Definition Return field is undefined", + fmt.Sprintf("Function %q - Definition Return field is undefined", req.FuncName), ) } else if d.Return.GetType() == nil { 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"+ - "Definition return data type is undefined", + fmt.Sprintf("Function %q - Definition return data type is undefined", req.FuncName), ) } else if returnWithValidateImplementation, ok := d.Return.(fwfunction.ReturnWithValidateImplementation); ok { req := fwfunction.ValidateReturnImplementationRequest{} @@ -120,9 +120,14 @@ func (d Definition) ValidateImplementation(ctx context.Context) diag.Diagnostics for pos, param := range d.Parameters { parameterPosition := int64(pos) name := param.GetName() - // If name is not set, default the param name based on position: "param1", "param2", etc. + // If name is not set, add an error diagnostic, parameter names are mandatory. if name == "" { - name = fmt.Sprintf("%s%d", DefaultParameterNamePrefix, pos+1) + 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 { @@ -138,13 +143,13 @@ func (d Definition) ValidateImplementation(ctx context.Context) diag.Diagnostics } conflictPos, exists := paramNames[name] - if exists { + if exists && 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"+ "Parameter names must be unique. "+ - fmt.Sprintf("Parameters at position %d and %d have the same name %q", conflictPos, pos, name), + fmt.Sprintf("Function %q - Parameters at position %d and %d have the same name %q", req.FuncName, conflictPos, pos, name), ) continue } @@ -154,9 +159,14 @@ func (d Definition) ValidateImplementation(ctx context.Context) diag.Diagnostics if d.VariadicParameter != nil { name := d.VariadicParameter.GetName() - // If name is not set, default the variadic param name + // If name is not set, add an error diagnostic, parameter names are mandatory. if name == "" { - name = DefaultVariadicParameterName + 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 { @@ -171,18 +181,18 @@ func (d Definition) ValidateImplementation(ctx context.Context) diag.Diagnostics } conflictPos, exists := paramNames[name] - if exists { + if exists && 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"+ "Parameter names must be unique. "+ - fmt.Sprintf("Parameter at position %d and the variadic parameter have the same name %q", conflictPos, name), + fmt.Sprintf("Function %q - Parameter at position %d and the variadic parameter have the same name %q", req.FuncName, conflictPos, name), ) } } - return diags + resp.Diagnostics.Append(diags...) } // DefinitionRequest represents a request for the Function to return its @@ -202,3 +212,21 @@ type DefinitionResponse struct { // An empty slice indicates success, with no warnings or errors generated. Diagnostics diag.Diagnostics } + +// DefinitionValidateRequest represents a request for the Function to validate its +// definition. An instance of this request struct is supplied as an argument to +// the Definition type ValidateImplementation method. +type DefinitionValidateRequest struct { + // FuncName is the name of the function definition being validated. + FuncName string +} + +// DefinitionValidateResponse represents a response to a DefinitionValidateRequest. +// An instance of this response struct is supplied as an argument to the Definition +// type ValidateImplementation method. +type DefinitionValidateResponse struct { + // Diagnostics report errors or warnings related to validation of a function + // definition. An empty slice indicates success, with no warnings or errors + // generated. + Diagnostics diag.Diagnostics +} diff --git a/function/definition_test.go b/function/definition_test.go index a9eff1bfe..d2f75b99f 100644 --- a/function/definition_test.go +++ b/function/definition_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/types" @@ -151,20 +152,31 @@ func TestDefinitionValidateImplementation(t *testing.T) { testCases := map[string]struct { definition function.Definition - expected diag.Diagnostics + expected function.DefinitionValidateResponse }{ "valid-no-params": { definition: function.Definition{ Return: function.StringReturn{}, }, + expected: function.DefinitionValidateResponse{}, }, - "valid-only-variadic": { + "missing-variadic-param-name": { definition: function.Definition{ VariadicParameter: function.StringParameter{}, Return: function.StringReturn{}, }, + expected: function.DefinitionValidateResponse{ + 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 \"test-function\" - The variadic parameter does not have a name", + ), + }, + }, }, - "valid-param-name-defaults": { + "missing-param-names": { definition: function.Definition{ Parameters: []function.Parameter{ function.StringParameter{}, @@ -172,8 +184,24 @@ func TestDefinitionValidateImplementation(t *testing.T) { }, Return: function.StringReturn{}, }, + expected: function.DefinitionValidateResponse{ + 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 \"test-function\" - Parameter at position 0 does not have a name", + ), + 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 \"test-function\" - Parameter at position 1 does not have a name", + ), + }, + }, }, - "valid-param-names-defaults-with-variadic": { + "missing-param-names-with-variadic": { definition: function.Definition{ Parameters: []function.Parameter{ function.StringParameter{}, @@ -181,58 +209,86 @@ func TestDefinitionValidateImplementation(t *testing.T) { VariadicParameter: function.NumberParameter{}, Return: function.StringReturn{}, }, + expected: function.DefinitionValidateResponse{ + 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 \"test-function\" - Parameter at position 0 does not have a name", + ), + 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 \"test-function\" - The variadic parameter does not have a name", + ), + }, + }, }, "result-missing": { definition: function.Definition{}, - expected: 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"+ - "Definition Return field is undefined", - ), + expected: function.DefinitionValidateResponse{ + 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 \"test-function\" - Definition Return field is undefined", + ), + }, }, }, "param-dynamic-in-collection": { definition: function.Definition{ Parameters: []function.Parameter{ function.MapParameter{ + Name: "map_with_dynamic", ElementType: types.DynamicType, }, }, Return: function.StringReturn{}, }, - expected: 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"+ - "Parameter \"param1\" at position 0 contains a collection type with a nested dynamic type.\n\n"+ - "Dynamic types inside of collections are not currently supported in terraform-plugin-framework. "+ - "If underlying dynamic values are required, replace the \"param1\" parameter definition with DynamicParameter instead.", - ), + expected: function.DefinitionValidateResponse{ + 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"+ + "Parameter \"map_with_dynamic\" at position 0 contains a collection type with a nested dynamic type.\n\n"+ + "Dynamic types inside of collections are not currently supported in terraform-plugin-framework. "+ + "If underlying dynamic values are required, replace the \"map_with_dynamic\" parameter definition with DynamicParameter instead.", + ), + }, }, }, "variadic-param-dynamic-in-collection": { definition: function.Definition{ Parameters: []function.Parameter{ - function.StringParameter{}, - function.StringParameter{}, + function.StringParameter{ + Name: "string_param1", + }, + function.StringParameter{ + Name: "string_param2", + }, }, VariadicParameter: function.SetParameter{ + Name: "set_with_dynamic", ElementType: types.DynamicType, }, Return: function.StringReturn{}, }, - expected: 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"+ - "Variadic parameter \"varparam\" contains a collection type with a nested dynamic type.\n\n"+ - "Dynamic types inside of collections are not currently supported in terraform-plugin-framework. "+ - "If underlying dynamic values are required, replace the variadic parameter definition with DynamicParameter instead.", - ), + expected: function.DefinitionValidateResponse{ + 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"+ + "Variadic parameter \"set_with_dynamic\" contains a collection type with a nested dynamic type.\n\n"+ + "Dynamic types inside of collections are not currently supported in terraform-plugin-framework. "+ + "If underlying dynamic values are required, replace the variadic parameter definition with DynamicParameter instead.", + ), + }, }, }, "return-dynamic-in-collection": { @@ -241,15 +297,17 @@ func TestDefinitionValidateImplementation(t *testing.T) { ElementType: types.DynamicType, }, }, - expected: 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"+ - "Return contains a collection type with a nested dynamic type.\n\n"+ - "Dynamic types inside of collections are not currently supported in terraform-plugin-framework. "+ - "If underlying dynamic values are required, replace the return definition with DynamicReturn instead.", - ), + expected: function.DefinitionValidateResponse{ + 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"+ + "Return contains a collection type with a nested dynamic type.\n\n"+ + "Dynamic types inside of collections are not currently supported in terraform-plugin-framework. "+ + "If underlying dynamic values are required, replace the return definition with DynamicReturn instead.", + ), + }, }, }, "conflicting-param-names": { @@ -273,34 +331,16 @@ func TestDefinitionValidateImplementation(t *testing.T) { }, Return: function.StringReturn{}, }, - expected: 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"+ - "Parameter names must be unique. "+ - "Parameters at position 2 and 4 have the same name \"param-dup\"", - ), - }, - }, - "conflicting-param-name-with-default": { - definition: function.Definition{ - Parameters: []function.Parameter{ - function.StringParameter{ - Name: "param2", - }, - function.Float64Parameter{}, // defaults to param2 + expected: function.DefinitionValidateResponse{ + 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"+ + "Parameter names must be unique. "+ + "Function \"test-function\" - Parameters at position 2 and 4 have the same name \"param-dup\"", + ), }, - Return: function.StringReturn{}, - }, - expected: 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"+ - "Parameter names must be unique. "+ - "Parameters at position 0 and 1 have the same name \"param2\"", - ), }, }, "conflicting-param-names-variadic": { @@ -321,14 +361,16 @@ func TestDefinitionValidateImplementation(t *testing.T) { }, Return: function.StringReturn{}, }, - expected: 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"+ - "Parameter names must be unique. "+ - "Parameter at position 1 and the variadic parameter have the same name \"param-dup\"", - ), + expected: function.DefinitionValidateResponse{ + 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"+ + "Parameter names must be unique. "+ + "Function \"test-function\" - Parameter at position 1 and the variadic parameter have the same name \"param-dup\"", + ), + }, }, }, "conflicting-param-names-variadic-multiple": { @@ -355,48 +397,30 @@ func TestDefinitionValidateImplementation(t *testing.T) { }, Return: function.StringReturn{}, }, - expected: 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"+ - "Parameter names must be unique. "+ - "Parameters at position 0 and 2 have the same name \"param-dup\"", - ), - 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"+ - "Parameter names must be unique. "+ - "Parameters at position 0 and 4 have the same name \"param-dup\"", - ), - 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"+ - "Parameter names must be unique. "+ - "Parameter at position 0 and the variadic parameter have the same name \"param-dup\"", - ), - }, - }, - "conflicting-param-name-with-variadic-default": { - definition: function.Definition{ - Parameters: []function.Parameter{ - function.Float64Parameter{ - Name: function.DefaultVariadicParameterName, - }, + expected: function.DefinitionValidateResponse{ + 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"+ + "Parameter names must be unique. "+ + "Function \"test-function\" - Parameters at position 0 and 2 have the same name \"param-dup\"", + ), + 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"+ + "Parameter names must be unique. "+ + "Function \"test-function\" - Parameters at position 0 and 4 have the same name \"param-dup\"", + ), + 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"+ + "Parameter names must be unique. "+ + "Function \"test-function\" - Parameter at position 0 and the variadic parameter have the same name \"param-dup\"", + ), }, - VariadicParameter: function.BoolParameter{}, // defaults to varparam - Return: function.StringReturn{}, - }, - expected: 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"+ - "Parameter names must be unique. "+ - "Parameter at position 0 and the variadic parameter have the same name \"varparam\"", - ), }, }, } @@ -407,7 +431,9 @@ func TestDefinitionValidateImplementation(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - got := testCase.definition.ValidateImplementation(context.Background()) + got := function.DefinitionValidateResponse{} + + testCase.definition.ValidateImplementation(context.Background(), function.DefinitionValidateRequest{FuncName: "test-function"}, &got) if diff := cmp.Diff(got, testCase.expected); diff != "" { t.Errorf("unexpected difference: %s", diff) diff --git a/function/func_error.go b/function/func_error.go index 78da0053b..4ce870a2f 100644 --- a/function/func_error.go +++ b/function/func_error.go @@ -69,6 +69,10 @@ func (fe *FuncError) Equal(other *FuncError) bool { // Error returns the error text. func (fe *FuncError) Error() string { + if fe == nil { + return "" + } + return fe.Text } diff --git a/function/func_error_test.go b/function/func_error_test.go index b5e7816c7..6232071e0 100644 --- a/function/func_error_test.go +++ b/function/func_error_test.go @@ -87,6 +87,42 @@ func TestFunctionError_Equal(t *testing.T) { } } +func TestFunctionError_Error(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + funcErr *function.FuncError + expected string + }{ + "nil": { + expected: "", + }, + "empty": { + funcErr: &function.FuncError{}, + expected: "", + }, + "text": { + funcErr: &function.FuncError{ + Text: "function error", + }, + expected: "function error", + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tc.funcErr.Error() + + if got != tc.expected { + t.Errorf("Unexpected response: got: %s, wanted: %s", got, tc.expected) + } + }) + } +} + func TestConcatFuncErrors(t *testing.T) { t.Parallel() diff --git a/function/parameter.go b/function/parameter.go index 14a9fbb5d..e5add8828 100644 --- a/function/parameter.go +++ b/function/parameter.go @@ -7,17 +7,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" ) -const ( - // DefaultParameterNamePrefix is the prefix used to default the name of parameters which do not declare - // a name. Use this to prevent Terraform errors for missing names. This prefix is used with the parameter - // position in a function definition to create a unique name (param1, param2, etc.) - DefaultParameterNamePrefix = "param" - - // DefaultVariadicParameterName is the default name given to a variadic parameter that does not declare - // a name. Use this to prevent Terraform errors for missing names. - DefaultVariadicParameterName = "varparam" -) - // Parameter is the interface for defining function parameters. type Parameter interface { // GetAllowNullValue should return if the parameter accepts a null value. diff --git a/internal/fwserver/server_functions.go b/internal/fwserver/server_functions.go index c8e6142d2..37c87e7c8 100644 --- a/internal/fwserver/server_functions.go +++ b/internal/fwserver/server_functions.go @@ -98,11 +98,17 @@ func (s *Server) FunctionDefinitions(ctx context.Context) (map[string]function.D continue } - validateDiags := definitionResp.Definition.ValidateImplementation(ctx) + validateReq := function.DefinitionValidateRequest{ + FuncName: name, + } + + validateResp := function.DefinitionValidateResponse{} + + definitionResp.Definition.ValidateImplementation(ctx, validateReq, &validateResp) - diags.Append(validateDiags...) + diags.Append(validateResp.Diagnostics...) - if validateDiags.HasError() { + if validateResp.Diagnostics.HasError() { continue } diff --git a/internal/fwserver/server_getfunctions_test.go b/internal/fwserver/server_getfunctions_test.go index 8aae0b625..1df383710 100644 --- a/internal/fwserver/server_getfunctions_test.go +++ b/internal/fwserver/server_getfunctions_test.go @@ -116,7 +116,7 @@ func TestServerGetFunctions(t *testing.T) { "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"+ - "Definition Return field is undefined", + "Function \"function1\" - Definition Return field is undefined", ), }, FunctionDefinitions: map[string]function.Definition{}, diff --git a/internal/fwserver/server_getproviderschema_test.go b/internal/fwserver/server_getproviderschema_test.go index edd2c0acf..3c975d11b 100644 --- a/internal/fwserver/server_getproviderschema_test.go +++ b/internal/fwserver/server_getproviderschema_test.go @@ -416,7 +416,7 @@ func TestServerGetProviderSchema(t *testing.T) { "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"+ - "Definition Return field is undefined", + "Function \"function1\" - Definition Return field is undefined", ), }, FunctionDefinitions: nil, diff --git a/internal/toproto5/function.go b/internal/toproto5/function.go index 7b4cfc071..5ee2e16e2 100644 --- a/internal/toproto5/function.go +++ b/internal/toproto5/function.go @@ -5,7 +5,6 @@ package toproto5 import ( "context" - "fmt" "github.com/hashicorp/terraform-plugin-go/tfprotov5" @@ -30,25 +29,13 @@ func Function(ctx context.Context, fw function.Definition) *tfprotov5.Function { proto.DescriptionKind = tfprotov5.StringKindPlain } - for i, fwParameter := range fw.Parameters { + for _, fwParameter := range fw.Parameters { protoParam := FunctionParameter(ctx, fwParameter) - - // If name is not set, default the param name based on position: "param1", "param2", etc. - if protoParam.Name == "" { - protoParam.Name = fmt.Sprintf("%s%d", function.DefaultParameterNamePrefix, i+1) - } - proto.Parameters = append(proto.Parameters, protoParam) } if fw.VariadicParameter != nil { protoParam := FunctionParameter(ctx, fw.VariadicParameter) - - // If name is not set, default the variadic param name - if protoParam.Name == "" { - protoParam.Name = function.DefaultVariadicParameterName - } - proto.VariadicParameter = protoParam } diff --git a/internal/toproto5/function_test.go b/internal/toproto5/function_test.go index 76d9138bd..758746e14 100644 --- a/internal/toproto5/function_test.go +++ b/internal/toproto5/function_test.go @@ -143,7 +143,7 @@ func TestFunction(t *testing.T) { }, }, }, - "parameters-defaults": { + "parameters-unnamed": { fw: function.Definition{ Parameters: []function.Parameter{ function.BoolParameter{}, @@ -156,20 +156,16 @@ func TestFunction(t *testing.T) { expected: &tfprotov5.Function{ Parameters: []*tfprotov5.FunctionParameter{ { - Name: "param1", Type: tftypes.Bool, }, { - Name: "param2", Type: tftypes.Number, }, { - Name: "param3", Type: tftypes.String, }, }, VariadicParameter: &tfprotov5.FunctionParameter{ - Name: function.DefaultVariadicParameterName, Type: tftypes.Number, }, Return: &tfprotov5.FunctionReturn{ @@ -212,7 +208,6 @@ func TestFunction(t *testing.T) { Type: tftypes.String, }, VariadicParameter: &tfprotov5.FunctionParameter{ - Name: function.DefaultVariadicParameterName, Type: tftypes.String, }, }, diff --git a/internal/toproto5/getfunctions_test.go b/internal/toproto5/getfunctions_test.go index 58bec8735..f90ed5a7b 100644 --- a/internal/toproto5/getfunctions_test.go +++ b/internal/toproto5/getfunctions_test.go @@ -8,12 +8,13 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" "github.com/hashicorp/terraform-plugin-framework/internal/toproto5" - "github.com/hashicorp/terraform-plugin-go/tfprotov5" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) func TestGetFunctionsResponse(t *testing.T) { @@ -138,15 +139,12 @@ func TestGetFunctionsResponse(t *testing.T) { "testfunction": { Parameters: []*tfprotov5.FunctionParameter{ { - Name: "param1", Type: tftypes.Bool, }, { - Name: "param2", Type: tftypes.Number, }, { - Name: "param3", Type: tftypes.String, }, }, @@ -214,7 +212,6 @@ func TestGetFunctionsResponse(t *testing.T) { Type: tftypes.String, }, VariadicParameter: &tfprotov5.FunctionParameter{ - Name: function.DefaultVariadicParameterName, Type: tftypes.String, }, }, diff --git a/internal/toproto5/getproviderschema_test.go b/internal/toproto5/getproviderschema_test.go index 83bd07a5a..c89fb31cd 100644 --- a/internal/toproto5/getproviderschema_test.go +++ b/internal/toproto5/getproviderschema_test.go @@ -8,6 +8,9 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" datasourceschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/function" @@ -18,8 +21,6 @@ import ( providerschema "github.com/hashicorp/terraform-plugin-framework/provider/schema" resourceschema "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov5" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) // TODO: Tuple type support @@ -1100,15 +1101,12 @@ func TestGetProviderSchemaResponse(t *testing.T) { "testfunction": { Parameters: []*tfprotov5.FunctionParameter{ { - Name: "param1", Type: tftypes.Bool, }, { - Name: "param2", Type: tftypes.Number, }, { - Name: "param3", Type: tftypes.String, }, }, @@ -1182,7 +1180,6 @@ func TestGetProviderSchemaResponse(t *testing.T) { Type: tftypes.String, }, VariadicParameter: &tfprotov5.FunctionParameter{ - Name: function.DefaultVariadicParameterName, Type: tftypes.String, }, }, diff --git a/internal/toproto6/function.go b/internal/toproto6/function.go index 90925be10..70edabf2e 100644 --- a/internal/toproto6/function.go +++ b/internal/toproto6/function.go @@ -5,7 +5,6 @@ package toproto6 import ( "context" - "fmt" "github.com/hashicorp/terraform-plugin-go/tfprotov6" @@ -30,25 +29,13 @@ func Function(ctx context.Context, fw function.Definition) *tfprotov6.Function { proto.DescriptionKind = tfprotov6.StringKindPlain } - for i, fwParameter := range fw.Parameters { + for _, fwParameter := range fw.Parameters { protoParam := FunctionParameter(ctx, fwParameter) - - // If name is not set, default the param name based on position: "param1", "param2", etc. - if protoParam.Name == "" { - protoParam.Name = fmt.Sprintf("%s%d", function.DefaultParameterNamePrefix, i+1) - } - proto.Parameters = append(proto.Parameters, protoParam) } if fw.VariadicParameter != nil { protoParam := FunctionParameter(ctx, fw.VariadicParameter) - - // If name is not set, default the variadic param name - if protoParam.Name == "" { - protoParam.Name = function.DefaultVariadicParameterName - } - proto.VariadicParameter = protoParam } diff --git a/internal/toproto6/function_test.go b/internal/toproto6/function_test.go index d26ca790c..47af4e72a 100644 --- a/internal/toproto6/function_test.go +++ b/internal/toproto6/function_test.go @@ -143,7 +143,7 @@ func TestFunction(t *testing.T) { }, }, }, - "parameters-defaults": { + "parameters-unnamed": { fw: function.Definition{ Parameters: []function.Parameter{ function.BoolParameter{}, @@ -156,20 +156,16 @@ func TestFunction(t *testing.T) { expected: &tfprotov6.Function{ Parameters: []*tfprotov6.FunctionParameter{ { - Name: "param1", Type: tftypes.Bool, }, { - Name: "param2", Type: tftypes.Number, }, { - Name: "param3", Type: tftypes.String, }, }, VariadicParameter: &tfprotov6.FunctionParameter{ - Name: function.DefaultVariadicParameterName, Type: tftypes.Number, }, Return: &tfprotov6.FunctionReturn{ @@ -212,7 +208,6 @@ func TestFunction(t *testing.T) { Type: tftypes.String, }, VariadicParameter: &tfprotov6.FunctionParameter{ - Name: function.DefaultVariadicParameterName, Type: tftypes.String, }, }, diff --git a/internal/toproto6/getfunctions_test.go b/internal/toproto6/getfunctions_test.go index 3b4dfa9f1..8de74f894 100644 --- a/internal/toproto6/getfunctions_test.go +++ b/internal/toproto6/getfunctions_test.go @@ -8,12 +8,13 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" "github.com/hashicorp/terraform-plugin-framework/internal/toproto6" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) func TestGetFunctionsResponse(t *testing.T) { @@ -138,15 +139,12 @@ func TestGetFunctionsResponse(t *testing.T) { "testfunction": { Parameters: []*tfprotov6.FunctionParameter{ { - Name: "param1", Type: tftypes.Bool, }, { - Name: "param2", Type: tftypes.Number, }, { - Name: "param3", Type: tftypes.String, }, }, @@ -214,7 +212,6 @@ func TestGetFunctionsResponse(t *testing.T) { Type: tftypes.String, }, VariadicParameter: &tfprotov6.FunctionParameter{ - Name: function.DefaultVariadicParameterName, Type: tftypes.String, }, }, diff --git a/internal/toproto6/getproviderschema_test.go b/internal/toproto6/getproviderschema_test.go index 60a405718..4ea37606c 100644 --- a/internal/toproto6/getproviderschema_test.go +++ b/internal/toproto6/getproviderschema_test.go @@ -8,6 +8,9 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" datasourceschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/function" @@ -18,8 +21,6 @@ import ( providerschema "github.com/hashicorp/terraform-plugin-framework/provider/schema" resourceschema "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) // TODO: Tuple type support @@ -1148,15 +1149,12 @@ func TestGetProviderSchemaResponse(t *testing.T) { "testfunction": { Parameters: []*tfprotov6.FunctionParameter{ { - Name: "param1", Type: tftypes.Bool, }, { - Name: "param2", Type: tftypes.Number, }, { - Name: "param3", Type: tftypes.String, }, }, @@ -1230,7 +1228,6 @@ func TestGetProviderSchemaResponse(t *testing.T) { Type: tftypes.String, }, VariadicParameter: &tfprotov6.FunctionParameter{ - Name: function.DefaultVariadicParameterName, Type: tftypes.String, }, }, diff --git a/website/docs/plugin/framework/functions/documentation.mdx b/website/docs/plugin/framework/functions/documentation.mdx index 91b1c293c..c7e7ec373 100644 --- a/website/docs/plugin/framework/functions/documentation.mdx +++ b/website/docs/plugin/framework/functions/documentation.mdx @@ -45,11 +45,11 @@ func (f *CidrContainsIpFunction) Definition(ctx context.Context, req function.De Each [parameter type](/terraform/plugin/framework/functions/parameters), whether in the definition `Parameters` or `VariadicParameter` field, implements the following fields: -| Field Name | Description | -|---|---| -| `Name` | Single word or abbreviation of parameter for function signature generation. If name is not provided, will default to the prefix "param" with a suffix of the position the parameter is in the function definition (e.g., `param1`, `param2`). If the parameter is variadic, the default name will be `varparam`. | -| `Description` | Documentation about the parameter and its expected values in plaintext format. | -| `MarkdownDescription` | Documentation about the parameter and its expected values in Markdown format. | +| Field Name | Description | +|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------| +| `Name` | **Required**: Single word or abbreviation of parameter for function signature generation. If name is not provided, a runtime error will be generated. | +| `Description` | Documentation about the parameter and its expected values in plaintext format. | +| `MarkdownDescription` | Documentation about the parameter and its expected values in Markdown format. | The name must be unique in the context of the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). It is used for documentation purposes and displayed in error diagnostics presented to practitioners. The name should delineate the purpose of the parameter, especially to disambiguate between multiple parameters, such as the words `cidr` and `ip` in a generated function signature like `cidr_contains_ip(cidr string, ip string) bool`. diff --git a/website/docs/plugin/framework/functions/implementation.mdx b/website/docs/plugin/framework/functions/implementation.mdx index 9fe731837..98c87e1f5 100644 --- a/website/docs/plugin/framework/functions/implementation.mdx +++ b/website/docs/plugin/framework/functions/implementation.mdx @@ -152,8 +152,14 @@ func (f *ExampleFunction) Definition(ctx context.Context, req function.Definitio resp.Definition = function.Definition{ // ... other fields ... Parameters: []function.Parameter{ - function.BoolParameter{}, - function.StringParameter{}, + function.BoolParameter{ + Name: "bool_param", + // ... other fields ... + }, + function.StringParameter{ + Name: "string_param", + // ... other fields ... + }, }, } } @@ -177,8 +183,14 @@ func (f *ExampleFunction) Definition(ctx context.Context, req function.Definitio resp.Definition = function.Definition{ // ... other fields ... Parameters: []function.Parameter{ - function.BoolParameter{}, - function.StringParameter{}, + function.BoolParameter{ + Name: "bool_param", + // ... other fields ... + }, + function.StringParameter{ + Name: "string_param", + // ... other fields ... + }, }, } } @@ -205,10 +217,14 @@ func (f *ExampleFunction) Definition(ctx context.Context, req function.Definitio resp.Definition = function.Definition{ // ... other fields ... Parameters: []function.Parameter{ - function.BoolParameter{}, + function.BoolParameter{ + Name: "bool_param", + // ... other fields ... + }, }, VariadicParameter: function.StringParameter{ Name: "variadic_param", + // ... other fields ... }, } } @@ -232,11 +248,18 @@ func (f *ExampleFunction) Definition(ctx context.Context, req function.Definitio resp.Definition = function.Definition{ // ... other fields ... Parameters: []function.Parameter{ - function.BoolParameter{}, - function.Int64Parameter{}, + function.BoolParameter{ + Name: "bool_param", + // ... other fields ... + }, + function.Int64Parameter{ + Name: "int64_param", + // ... other fields ... + }, }, VariadicParameter: function.StringParameter{ Name: "variadic_param", + // ... other fields ... }, } } diff --git a/website/docs/plugin/framework/functions/parameters/bool.mdx b/website/docs/plugin/framework/functions/parameters/bool.mdx index 7607f1bb3..7e58f771f 100644 --- a/website/docs/plugin/framework/functions/parameters/bool.mdx +++ b/website/docs/plugin/framework/functions/parameters/bool.mdx @@ -26,6 +26,7 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition // ... other Definition fields ... Parameters: []function.Parameter{ function.BoolParameter{ + Name: "example", // ... potentially other BoolParameter fields ... }, }, @@ -73,7 +74,9 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition resp.Definition = function.Definition{ // ... other Definition fields ... Parameters: []function.Parameter{ - function.BoolParameter{}, + function.BoolParameter{ + Name: "bool_param", + }, }, } } diff --git a/website/docs/plugin/framework/functions/parameters/float64.mdx b/website/docs/plugin/framework/functions/parameters/float64.mdx index d9b26196d..11f97239f 100644 --- a/website/docs/plugin/framework/functions/parameters/float64.mdx +++ b/website/docs/plugin/framework/functions/parameters/float64.mdx @@ -32,6 +32,7 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition // ... other Definition fields ... Parameters: []function.Parameter{ function.Float64Parameter{ + Name: "float64_param", // ... potentially other Float64Parameter fields ... }, }, @@ -79,7 +80,10 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition resp.Definition = function.Definition{ // ... other Definition fields ... Parameters: []function.Parameter{ - function.Float64Parameter{}, + function.Float64Parameter{ + Name: "float64_param", + // ... potentially other Float64Parameter fields ... + }, }, } } diff --git a/website/docs/plugin/framework/functions/parameters/index.mdx b/website/docs/plugin/framework/functions/parameters/index.mdx index a175fe505..e0f23042c 100644 --- a/website/docs/plugin/framework/functions/parameters/index.mdx +++ b/website/docs/plugin/framework/functions/parameters/index.mdx @@ -60,4 +60,37 @@ Parameter that accepts any value type, determined by Terraform at runtime. | Parameter Type | Use Case | |----------------|----------| -| [Dynamic](/terraform/plugin/framework/functions/parameters/dynamic) | Accept any value type of data, determined at runtime. | \ No newline at end of file +| [Dynamic](/terraform/plugin/framework/functions/parameters/dynamic) | Accept any value type of data, determined at runtime. | + +## Parameter Naming + +All parameter types have a `Name` field that is **required**. + +### Missing Parameter Names + +Attempting to use unnamed parameters will generate runtime errors of the following form: + +```text +│ Error: Failed to load plugin schemas +│ +│ Error while loading schemas for plugin components: Failed to obtain provider schema: Could not load the schema for provider registry.terraform.io/cloud_provider/cloud_resource: failed to +│ retrieve schema from provider "registry.terraform.io/cloud_provider/cloud_resource": 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. +│ +│ Function "example_function" - Parameter at position 0 does not have a name. +``` + +### Parameter Errors + +Parameter names are used in runtime errors to highlight which parameter is causing the issue. For example, using a value that is incompatible with the parameter type will generate an error message such as the following: + +```text +│ Error: Invalid function argument +│ +│ on resource.tf line 10, in resource "example_resource" "example": +│ 10: configurable_attribute = provider::example::example_function("string") +│ ├──────────────── +│ │ while calling provider::example::example_function(bool_param) +│ +│ Invalid value for "bool_param" parameter: a bool is required. +``` diff --git a/website/docs/plugin/framework/functions/parameters/int64.mdx b/website/docs/plugin/framework/functions/parameters/int64.mdx index e09432547..ab3b272d2 100644 --- a/website/docs/plugin/framework/functions/parameters/int64.mdx +++ b/website/docs/plugin/framework/functions/parameters/int64.mdx @@ -32,6 +32,7 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition // ... other Definition fields ... Parameters: []function.Parameter{ function.Int64Parameter{ + Name: "int64_param", // ... potentially other Int64Parameter fields ... }, }, @@ -79,7 +80,10 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition resp.Definition = function.Definition{ // ... other Definition fields ... Parameters: []function.Parameter{ - function.Int64Parameter{}, + function.Int64Parameter{ + Name: "int64_param", + // ... potentially other Int64Parameter fields ... + }, }, } } diff --git a/website/docs/plugin/framework/functions/parameters/list.mdx b/website/docs/plugin/framework/functions/parameters/list.mdx index 9109ba680..30da0e91a 100644 --- a/website/docs/plugin/framework/functions/parameters/list.mdx +++ b/website/docs/plugin/framework/functions/parameters/list.mdx @@ -29,6 +29,7 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition Parameters: []function.Parameter{ function.ListParameter{ ElementType: types.StringType, + Name: "list_param", // ... potentially other ListParameter fields ... }, }, @@ -83,6 +84,7 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition Parameters: []function.Parameter{ function.ListParameter{ ElementType: types.StringType, + Name: "list_param", }, }, } diff --git a/website/docs/plugin/framework/functions/parameters/map.mdx b/website/docs/plugin/framework/functions/parameters/map.mdx index 158a2612b..8eda096fa 100644 --- a/website/docs/plugin/framework/functions/parameters/map.mdx +++ b/website/docs/plugin/framework/functions/parameters/map.mdx @@ -32,6 +32,7 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition Parameters: []function.Parameter{ function.MapParameter{ ElementType: types.StringType, + Name: "map_param", // ... potentially other MapParameter fields ... }, }, @@ -86,6 +87,8 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition Parameters: []function.Parameter{ function.MapParameter{ ElementType: types.StringType, + Name: "map_param", + // ... potentially other MapParameter fields ... }, }, } diff --git a/website/docs/plugin/framework/functions/parameters/number.mdx b/website/docs/plugin/framework/functions/parameters/number.mdx index f97a40289..b5e30fb70 100644 --- a/website/docs/plugin/framework/functions/parameters/number.mdx +++ b/website/docs/plugin/framework/functions/parameters/number.mdx @@ -32,6 +32,7 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition // ... other Definition fields ... Parameters: []function.Parameter{ function.NumberParameter{ + Name: "number_param", // ... potentially other NumberParameter fields ... }, }, @@ -78,7 +79,10 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition resp.Definition = function.Definition{ // ... other Definition fields ... Parameters: []function.Parameter{ - function.NumberParameter{}, + function.NumberParameter{ + Name: "number_param", + // ... potentially other NumberParameter fields ... + }, }, } } diff --git a/website/docs/plugin/framework/functions/parameters/object.mdx b/website/docs/plugin/framework/functions/parameters/object.mdx index edd2443ed..dd478f854 100644 --- a/website/docs/plugin/framework/functions/parameters/object.mdx +++ b/website/docs/plugin/framework/functions/parameters/object.mdx @@ -39,6 +39,7 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition "attr2": types.Int64Type, "attr3": types.BoolType, }, + Name: "object_param", // ... potentially other ObjectParameter fields ... }, }, @@ -98,6 +99,7 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition "attr2": types.Int64Type, "attr3": types.BoolType, }, + Name: "object_param", }, }, } diff --git a/website/docs/plugin/framework/functions/parameters/set.mdx b/website/docs/plugin/framework/functions/parameters/set.mdx index dfef8490c..ccd8117c5 100644 --- a/website/docs/plugin/framework/functions/parameters/set.mdx +++ b/website/docs/plugin/framework/functions/parameters/set.mdx @@ -29,6 +29,7 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition Parameters: []function.Parameter{ function.SetParameter{ ElementType: types.StringType, + Name: "set_param", // ... potentially other SetParameter fields ... }, }, @@ -83,6 +84,8 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition Parameters: []function.Parameter{ function.SetParameter{ ElementType: types.StringType, + Name: "set_param", + // ... potentially other SetParameter fields ... }, }, } diff --git a/website/docs/plugin/framework/functions/parameters/string.mdx b/website/docs/plugin/framework/functions/parameters/string.mdx index 5f04df8af..c11e33b5c 100644 --- a/website/docs/plugin/framework/functions/parameters/string.mdx +++ b/website/docs/plugin/framework/functions/parameters/string.mdx @@ -26,6 +26,7 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition // ... other Definition fields ... Parameters: []function.Parameter{ function.StringParameter{ + Name: "string_param", // ... potentially other StringParameter fields ... }, }, @@ -73,7 +74,10 @@ func (f ExampleFunction) Definition(ctx context.Context, req function.Definition resp.Definition = function.Definition{ // ... other Definition fields ... Parameters: []function.Parameter{ - function.StringParameter{}, + function.StringParameter{ + Name: "string_param", + // ... potentially other StringParameter fields ... + }, }, } }