// Copyright (c) The OpenTofu Authors // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2023 HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package jsonentities import ( "encoding/json" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" ) // Function is a description of the JSON representation of the signature of // a function callable from the OpenTofu language. type Function struct { // Name is the leaf name of the function, without any namespace prefix. Name string `json:"name"` Params []FunctionParam `json:"params"` VariadicParam *FunctionParam `json:"variadic_param,omitempty"` // ReturnType is type constraint which is a static approximation of the // possibly-dynamic return type of the function. ReturnType json.RawMessage `json:"return_type"` Description string `json:"description,omitempty"` DescriptionKind string `json:"description_kind,omitempty"` } // FunctionParam represents a single parameter to a function, as represented // by type Function. type FunctionParam struct { // Name is a name for the function which is used primarily for // documentation purposes, because function arguments are positional // and therefore don't appear directly in configuration source code. Name string `json:"name"` // Type is a type constraint which is a static approximation of the // possibly-dynamic type of the parameter. Particular functions may // have additional requirements that a type constraint alone cannot // represent. Type json.RawMessage `json:"type"` // Maybe some of the other fields in function.Parameter would be // interesting to describe here too, but we'll wait to see if there // is a use-case first. Description string `json:"description,omitempty"` DescriptionKind string `json:"description_kind,omitempty"` } // DescribeFunction returns a description of the signature of the given cty // function, as a pointer to this package's serializable type Function. func DescribeFunction(name string, f function.Function) *Function { ret := &Function{ Name: name, } params := f.Params() ret.Params = make([]FunctionParam, len(params)) typeCheckArgs := make([]cty.Type, len(params), len(params)+1) for i, param := range params { ret.Params[i] = describeFunctionParam(¶m) typeCheckArgs[i] = param.Type } if varParam := f.VarParam(); varParam != nil { descParam := describeFunctionParam(varParam) ret.VariadicParam = &descParam typeCheckArgs = append(typeCheckArgs, varParam.Type) } retType, err := f.ReturnType(typeCheckArgs) if err != nil { // Getting an error when type-checking with exactly the type constraints // the function called for is weird, so we'll just treat it as if it // has a dynamic return type instead, for our purposes here. // One reason this can happen is for a function which has a variadic // parameter but has logic inside it which considers it invalid to // specify exactly one argument for that parameter (since that's what // we did in typeCheckArgs as an approximation of a valid call above.) retType = cty.DynamicPseudoType } if raw, err := retType.MarshalJSON(); err != nil { // Again, we'll treat any errors as if the function is dynamically // typed because it would be weird to get here. ret.ReturnType = json.RawMessage(`"dynamic"`) } else { ret.ReturnType = json.RawMessage(raw) } // We don't currently have any sense of descriptions for functions and // their parameters, so we'll just leave those fields unpopulated for now. return ret } func describeFunctionParam(p *function.Parameter) FunctionParam { ret := FunctionParam{ Name: p.Name, } if raw, err := p.Type.MarshalJSON(); err != nil { // We'll treat any errors as if the function is dynamically // typed because it would be weird to get here. ret.Type = json.RawMessage(`"dynamic"`) } else { ret.Type = json.RawMessage(raw) } // We don't currently have any sense of descriptions for functions and // their parameters, so we'll just leave those fields unpopulated for now. return ret }