github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/views/json/function.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package json
     5  
     6  import (
     7  	"encoding/json"
     8  
     9  	"github.com/zclconf/go-cty/cty"
    10  	"github.com/zclconf/go-cty/cty/function"
    11  )
    12  
    13  // Function is a description of the JSON representation of the signature of
    14  // a function callable from the Terraform language.
    15  type Function struct {
    16  	// Name is the leaf name of the function, without any namespace prefix.
    17  	Name string `json:"name"`
    18  
    19  	Params        []FunctionParam `json:"params"`
    20  	VariadicParam *FunctionParam  `json:"variadic_param,omitempty"`
    21  
    22  	// ReturnType is type constraint which is a static approximation of the
    23  	// possibly-dynamic return type of the function.
    24  	ReturnType json.RawMessage `json:"return_type"`
    25  
    26  	Description     string `json:"description,omitempty"`
    27  	DescriptionKind string `json:"description_kind,omitempty"`
    28  }
    29  
    30  // FunctionParam represents a single parameter to a function, as represented
    31  // by type Function.
    32  type FunctionParam struct {
    33  	// Name is a name for the function which is used primarily for
    34  	// documentation purposes, because function arguments are positional
    35  	// and therefore don't appear directly in configuration source code.
    36  	Name string `json:"name"`
    37  
    38  	// Type is a type constraint which is a static approximation of the
    39  	// possibly-dynamic type of the parameter. Particular functions may
    40  	// have additional requirements that a type constraint alone cannot
    41  	// represent.
    42  	Type json.RawMessage `json:"type"`
    43  
    44  	// Maybe some of the other fields in function.Parameter would be
    45  	// interesting to describe here too, but we'll wait to see if there
    46  	// is a use-case first.
    47  
    48  	Description     string `json:"description,omitempty"`
    49  	DescriptionKind string `json:"description_kind,omitempty"`
    50  }
    51  
    52  // DescribeFunction returns a description of the signature of the given cty
    53  // function, as a pointer to this package's serializable type Function.
    54  func DescribeFunction(name string, f function.Function) *Function {
    55  	ret := &Function{
    56  		Name: name,
    57  	}
    58  
    59  	params := f.Params()
    60  	ret.Params = make([]FunctionParam, len(params))
    61  	typeCheckArgs := make([]cty.Type, len(params), len(params)+1)
    62  	for i, param := range params {
    63  		ret.Params[i] = describeFunctionParam(&param)
    64  		typeCheckArgs[i] = param.Type
    65  	}
    66  	if varParam := f.VarParam(); varParam != nil {
    67  		descParam := describeFunctionParam(varParam)
    68  		ret.VariadicParam = &descParam
    69  		typeCheckArgs = append(typeCheckArgs, varParam.Type)
    70  	}
    71  
    72  	retType, err := f.ReturnType(typeCheckArgs)
    73  	if err != nil {
    74  		// Getting an error when type-checking with exactly the type constraints
    75  		// the function called for is weird, so we'll just treat it as if it
    76  		// has a dynamic return type instead, for our purposes here.
    77  		// One reason this can happen is for a function which has a variadic
    78  		// parameter but has logic inside it which considers it invalid to
    79  		// specify exactly one argument for that parameter (since that's what
    80  		// we did in typeCheckArgs as an approximation of a valid call above.)
    81  		retType = cty.DynamicPseudoType
    82  	}
    83  
    84  	if raw, err := retType.MarshalJSON(); err != nil {
    85  		// Again, we'll treat any errors as if the function is dynamically
    86  		// typed because it would be weird to get here.
    87  		ret.ReturnType = json.RawMessage(`"dynamic"`)
    88  	} else {
    89  		ret.ReturnType = json.RawMessage(raw)
    90  	}
    91  
    92  	// We don't currently have any sense of descriptions for functions and
    93  	// their parameters, so we'll just leave those fields unpopulated for now.
    94  
    95  	return ret
    96  }
    97  
    98  func describeFunctionParam(p *function.Parameter) FunctionParam {
    99  	ret := FunctionParam{
   100  		Name: p.Name,
   101  	}
   102  
   103  	if raw, err := p.Type.MarshalJSON(); err != nil {
   104  		// We'll treat any errors as if the function is dynamically
   105  		// typed because it would be weird to get here.
   106  		ret.Type = json.RawMessage(`"dynamic"`)
   107  	} else {
   108  		ret.Type = json.RawMessage(raw)
   109  	}
   110  
   111  	// We don't currently have any sense of descriptions for functions and
   112  	// their parameters, so we'll just leave those fields unpopulated for now.
   113  
   114  	return ret
   115  }