// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package jsonfunction import ( "encoding/json" "fmt" "github.com/opentofu/opentofu/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. 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"` // 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 Marshal(f map[string]function.Function) ([]byte, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics signatures := newFunctions() for name, v := range f { if name == "can" { signatures.Signatures[name] = marshalCan(v) } else if name == "try" { signatures.Signatures[name] = marshalTry(v) } else { 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 } // 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, }, } } // 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, }, }, } }