github.com/hashicorp/hcl/v2@v2.20.0/ext/userfunc/decode.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package userfunc
     5  
     6  import (
     7  	"github.com/hashicorp/hcl/v2"
     8  	"github.com/zclconf/go-cty/cty"
     9  	"github.com/zclconf/go-cty/cty/function"
    10  )
    11  
    12  var funcBodySchema = &hcl.BodySchema{
    13  	Attributes: []hcl.AttributeSchema{
    14  		{
    15  			Name:     "params",
    16  			Required: true,
    17  		},
    18  		{
    19  			Name:     "variadic_param",
    20  			Required: false,
    21  		},
    22  		{
    23  			Name:     "result",
    24  			Required: true,
    25  		},
    26  	},
    27  }
    28  
    29  func decodeUserFunctions(body hcl.Body, blockType string, contextFunc ContextFunc) (funcs map[string]function.Function, remain hcl.Body, diags hcl.Diagnostics) {
    30  	schema := &hcl.BodySchema{
    31  		Blocks: []hcl.BlockHeaderSchema{
    32  			{
    33  				Type:       blockType,
    34  				LabelNames: []string{"name"},
    35  			},
    36  		},
    37  	}
    38  
    39  	content, remain, diags := body.PartialContent(schema)
    40  	if diags.HasErrors() {
    41  		return nil, remain, diags
    42  	}
    43  
    44  	// first call to getBaseCtx will populate context, and then the same
    45  	// context will be used for all subsequent calls. It's assumed that
    46  	// all functions in a given body should see an identical context.
    47  	var baseCtx *hcl.EvalContext
    48  	getBaseCtx := func() *hcl.EvalContext {
    49  		if baseCtx == nil {
    50  			if contextFunc != nil {
    51  				baseCtx = contextFunc()
    52  			}
    53  		}
    54  		// baseCtx might still be nil here, and that's okay
    55  		return baseCtx
    56  	}
    57  
    58  	funcs = make(map[string]function.Function)
    59  Blocks:
    60  	for _, block := range content.Blocks {
    61  		name := block.Labels[0]
    62  		funcContent, funcDiags := block.Body.Content(funcBodySchema)
    63  		diags = append(diags, funcDiags...)
    64  		if funcDiags.HasErrors() {
    65  			continue
    66  		}
    67  
    68  		paramsExpr := funcContent.Attributes["params"].Expr
    69  		resultExpr := funcContent.Attributes["result"].Expr
    70  		var varParamExpr hcl.Expression
    71  		if funcContent.Attributes["variadic_param"] != nil {
    72  			varParamExpr = funcContent.Attributes["variadic_param"].Expr
    73  		}
    74  
    75  		var params []string
    76  		var varParam string
    77  
    78  		paramExprs, paramsDiags := hcl.ExprList(paramsExpr)
    79  		diags = append(diags, paramsDiags...)
    80  		if paramsDiags.HasErrors() {
    81  			continue
    82  		}
    83  		for _, paramExpr := range paramExprs {
    84  			param := hcl.ExprAsKeyword(paramExpr)
    85  			if param == "" {
    86  				diags = append(diags, &hcl.Diagnostic{
    87  					Severity: hcl.DiagError,
    88  					Summary:  "Invalid param element",
    89  					Detail:   "Each parameter name must be an identifier.",
    90  					Subject:  paramExpr.Range().Ptr(),
    91  				})
    92  				continue Blocks
    93  			}
    94  			params = append(params, param)
    95  		}
    96  
    97  		if varParamExpr != nil {
    98  			varParam = hcl.ExprAsKeyword(varParamExpr)
    99  			if varParam == "" {
   100  				diags = append(diags, &hcl.Diagnostic{
   101  					Severity: hcl.DiagError,
   102  					Summary:  "Invalid variadic_param",
   103  					Detail:   "The variadic parameter name must be an identifier.",
   104  					Subject:  varParamExpr.Range().Ptr(),
   105  				})
   106  				continue
   107  			}
   108  		}
   109  
   110  		spec := &function.Spec{}
   111  		for _, paramName := range params {
   112  			spec.Params = append(spec.Params, function.Parameter{
   113  				Name: paramName,
   114  				Type: cty.DynamicPseudoType,
   115  			})
   116  		}
   117  		if varParamExpr != nil {
   118  			spec.VarParam = &function.Parameter{
   119  				Name: varParam,
   120  				Type: cty.DynamicPseudoType,
   121  			}
   122  		}
   123  		impl := func(args []cty.Value) (cty.Value, error) {
   124  			ctx := getBaseCtx()
   125  			ctx = ctx.NewChild()
   126  			ctx.Variables = make(map[string]cty.Value)
   127  
   128  			// The cty function machinery guarantees that we have at least
   129  			// enough args to fill all of our params.
   130  			for i, paramName := range params {
   131  				ctx.Variables[paramName] = args[i]
   132  			}
   133  			if spec.VarParam != nil {
   134  				varArgs := args[len(params):]
   135  				ctx.Variables[varParam] = cty.TupleVal(varArgs)
   136  			}
   137  
   138  			result, diags := resultExpr.Value(ctx)
   139  			if diags.HasErrors() {
   140  				// Smuggle the diagnostics out via the error channel, since
   141  				// a diagnostics sequence implements error. Caller can
   142  				// type-assert this to recover the individual diagnostics
   143  				// if desired.
   144  				return cty.DynamicVal, diags
   145  			}
   146  			return result, nil
   147  		}
   148  		spec.Type = func(args []cty.Value) (cty.Type, error) {
   149  			val, err := impl(args)
   150  			return val.Type(), err
   151  		}
   152  		spec.Impl = func(args []cty.Value, retType cty.Type) (cty.Value, error) {
   153  			return impl(args)
   154  		}
   155  		funcs[name] = function.New(spec)
   156  	}
   157  
   158  	return funcs, remain, diags
   159  }