github.com/opentofu/opentofu@v1.7.1/internal/command/views/json/function.go (about)

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