| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package jsonfunction | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "github.com/hashicorp/terraform/internal/providers" | |
| "github.com/hashicorp/terraform/internal/tfdiags" | |
| "github.com/zclconf/go-cty/cty" | |
| "github.com/zclconf/go-cty/cty/function" | |
| ) | |
| // FormatVersion represents the version of the json format and will be | |
| // incremented for any change to this format that requires changes to a | |
| // consuming parser. | |
| // | |
| // Any changes to this version should also consider compatibility in the | |
| // jsonprovider package versioning as well, as that functionality is also | |
| // reliant on this package. | |
| const FormatVersion = "1.0" | |
| // functions is the top-level object returned when exporting function signatures | |
| type functions struct { | |
| FormatVersion string `json:"format_version"` | |
| Signatures map[string]*FunctionSignature `json:"function_signatures,omitempty"` | |
| } | |
| // FunctionSignature represents a function signature. | |
| type FunctionSignature struct { | |
| // Description is an optional human-readable description | |
| // of the function | |
| Description string `json:"description,omitempty"` | |
| // Summary is the optional shortened description of the function | |
| Summary string `json:"summary,omitempty"` | |
| // DeprecationMessage is an optional message that indicates that the | |
| // function should be considered deprecated and what actions should be | |
| // performed by the practitioner to handle the deprecation. | |
| DeprecationMessage string `json:"deprecation_message,omitempty"` | |
| // ReturnTypes is the ctyjson representation of the function's | |
| // return types based on supplying all parameters using | |
| // dynamic types. Functions can have dynamic return types. | |
| ReturnType cty.Type `json:"return_type"` | |
| // Parameters describes the function's fixed positional parameters. | |
| Parameters []*parameter `json:"parameters,omitempty"` | |
| // VariadicParameter describes the function's variadic | |
| // parameters, if any are supported. | |
| VariadicParameter *parameter `json:"variadic_parameter,omitempty"` | |
| } | |
| func newFunctions() *functions { | |
| signatures := make(map[string]*FunctionSignature) | |
| return &functions{ | |
| FormatVersion: FormatVersion, | |
| Signatures: signatures, | |
| } | |
| } | |
| func MarshalProviderFunctions(f map[string]providers.FunctionDecl) map[string]*FunctionSignature { | |
| if f == nil { | |
| return nil | |
| } | |
| result := make(map[string]*FunctionSignature, len(f)) | |
| for name, v := range f { | |
| result[name] = marshalProviderFunction(v) | |
| } | |
| return result | |
| } | |
| func Marshal(f map[string]function.Function) ([]byte, tfdiags.Diagnostics) { | |
| var diags tfdiags.Diagnostics | |
| signatures := newFunctions() | |
| for name, v := range f { | |
| switch name { | |
| case "can", "core::can": | |
| signatures.Signatures[name] = marshalCan(v) | |
| case "try", "core::try": | |
| signatures.Signatures[name] = marshalTry(v) | |
| case "templatestring", "core::templatestring": | |
| signatures.Signatures[name] = marshalTemplatestring(v) | |
| case "convert", "core::convert": | |
| signatures.Signatures[name] = marshalConvert(v) | |
| default: | |
| signature, err := marshalFunction(v) | |
| if err != nil { | |
| diags = diags.Append(tfdiags.Sourceless( | |
| tfdiags.Error, | |
| fmt.Sprintf("Failed to serialize function %q", name), | |
| err.Error(), | |
| )) | |
| } | |
| signatures.Signatures[name] = signature | |
| } | |
| } | |
| if diags.HasErrors() { | |
| return nil, diags | |
| } | |
| ret, err := json.Marshal(signatures) | |
| if err != nil { | |
| diags = diags.Append(tfdiags.Sourceless( | |
| tfdiags.Error, | |
| "Failed to serialize functions", | |
| err.Error(), | |
| )) | |
| return nil, diags | |
| } | |
| return ret, nil | |
| } | |
| func marshalFunction(f function.Function) (*FunctionSignature, error) { | |
| var err error | |
| var vp *parameter | |
| if f.VarParam() != nil { | |
| vp = marshalParameter(f.VarParam()) | |
| } | |
| var p []*parameter | |
| if len(f.Params()) > 0 { | |
| p = marshalParameters(f.Params()) | |
| } | |
| r, err := getReturnType(f) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return &FunctionSignature{ | |
| Description: f.Description(), | |
| ReturnType: r, | |
| Parameters: p, | |
| VariadicParameter: vp, | |
| }, nil | |
| } | |
| func marshalProviderFunction(f providers.FunctionDecl) *FunctionSignature { | |
| var vp *parameter | |
| if f.VariadicParameter != nil { | |
| vp = marshalProviderParameter(*f.VariadicParameter) | |
| } | |
| var p []*parameter | |
| if len(f.Parameters) > 0 { | |
| p = marshalProviderParameters(f.Parameters) | |
| } | |
| return &FunctionSignature{ | |
| Description: f.Description, | |
| Summary: f.Summary, | |
| DeprecationMessage: f.DeprecationMessage, | |
| ReturnType: f.ReturnType, | |
| Parameters: p, | |
| VariadicParameter: vp, | |
| } | |
| } | |
| // marshalTry returns a static function signature for the try function. | |
| // We need this exception because the function implementation uses capsule | |
| // types that we can't marshal. | |
| func marshalTry(try function.Function) *FunctionSignature { | |
| return &FunctionSignature{ | |
| Description: try.Description(), | |
| ReturnType: cty.DynamicPseudoType, | |
| VariadicParameter: ¶meter{ | |
| Name: try.VarParam().Name, | |
| Description: try.VarParam().Description, | |
| IsNullable: try.VarParam().AllowNull, | |
| Type: cty.DynamicPseudoType, | |
| }, | |
| } | |
| } | |
| // marshalConvert returns a static function signature for the try function. | |
| // We need this exception because the function implementation uses capsule | |
| // types that we can't marshal. | |
| func marshalConvert(convert function.Function) *FunctionSignature { | |
| return &FunctionSignature{ | |
| Description: convert.Description(), | |
| ReturnType: cty.DynamicPseudoType, | |
| Parameters: []*parameter{ | |
| { | |
| Name: convert.Params()[0].Name, | |
| Description: convert.Params()[0].Description, | |
| IsNullable: convert.Params()[0].AllowNull, | |
| Type: cty.DynamicPseudoType, | |
| }, | |
| { | |
| Name: convert.Params()[1].Name, | |
| Description: convert.Params()[1].Description, | |
| Type: cty.DynamicPseudoType, | |
| }, | |
| }, | |
| } | |
| } | |
| // marshalCan returns a static function signature for the can function. | |
| // We need this exception because the function implementation uses capsule | |
| // types that we can't marshal. | |
| func marshalCan(can function.Function) *FunctionSignature { | |
| return &FunctionSignature{ | |
| Description: can.Description(), | |
| ReturnType: cty.Bool, | |
| Parameters: []*parameter{ | |
| { | |
| Name: can.Params()[0].Name, | |
| Description: can.Params()[0].Description, | |
| IsNullable: can.Params()[0].AllowNull, | |
| Type: cty.DynamicPseudoType, | |
| }, | |
| }, | |
| } | |
| } | |
| // marshalTemplatestring returns a static function signature for the | |
| // templatestring function. | |
| // We need this exception because the function implementation uses capsule | |
| // types that we can't marshal. | |
| func marshalTemplatestring(templatestring function.Function) *FunctionSignature { | |
| return &FunctionSignature{ | |
| Description: templatestring.Description(), | |
| ReturnType: cty.String, | |
| Parameters: []*parameter{ | |
| { | |
| Name: templatestring.Params()[0].Name, | |
| Description: templatestring.Params()[0].Description, | |
| IsNullable: templatestring.Params()[0].AllowNull, | |
| Type: cty.String, | |
| }, | |
| { | |
| Name: templatestring.Params()[1].Name, | |
| Description: templatestring.Params()[1].Description, | |
| IsNullable: templatestring.Params()[1].AllowNull, | |
| Type: cty.DynamicPseudoType, | |
| }, | |
| }, | |
| } | |
| } |