github.com/avenga/couper@v1.12.2/eval/value.go (about)

     1  package eval
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  
     7  	"github.com/hashicorp/hcl/v2"
     8  	"github.com/hashicorp/hcl/v2/hclsyntax"
     9  	"github.com/zclconf/go-cty/cty"
    10  
    11  	"github.com/avenga/couper/errors"
    12  )
    13  
    14  type ValueFunc interface {
    15  	Value(*hcl.EvalContext, hcl.Expression) (cty.Value, hcl.Diagnostics)
    16  }
    17  
    18  var emptyStringVal = cty.StringVal("")
    19  
    20  // ValueFromBodyAttribute lookups the given attribute from given hcl.Body and
    21  // returns cty.NilVal if the attribute is not present.
    22  func ValueFromBodyAttribute(ctx *hcl.EvalContext, body *hclsyntax.Body, name string) (cty.Value, error) {
    23  	if body == nil {
    24  		return cty.NilVal, nil
    25  	}
    26  
    27  	attr, ok := body.Attributes[name]
    28  	if !ok {
    29  		return cty.NilVal, nil
    30  	}
    31  
    32  	return Value(ctx, attr.Expr)
    33  }
    34  
    35  // Value is used to clone and modify the given expression if an expression would make use of
    36  // undefined context variables.
    37  // Effectively results in cty.NilVal or empty string value for template expression.
    38  //
    39  // A common case would be accessing a deeper nested structure which MAY be incomplete.
    40  // This replacement prevents returning unknown cty.Value's which could not be processed.
    41  func Value(ctx *hcl.EvalContext, exp hcl.Expression) (cty.Value, error) {
    42  	expr := exp
    43  	// due to some internal types we could not clone all expressions.
    44  	if _, ok := exp.(hclsyntax.Expression); ok {
    45  		expr = clone(exp)
    46  
    47  		// replace hcl expressions with literal ones if there is no context variable reference.
    48  		if val := newLiteralValueExpr(ctx, expr); val != nil {
    49  			expr = val
    50  		}
    51  	}
    52  
    53  	v, diags := expr.Value(ctx)
    54  	if diags.HasErrors() {
    55  		return v, errors.Evaluation.With(diags)
    56  	}
    57  	return v, nil
    58  }
    59  
    60  func newLiteralValueExpr(ctx *hcl.EvalContext, exp hcl.Expression) hclsyntax.Expression {
    61  	switch expr := exp.(type) {
    62  	case *hclsyntax.ConditionalExpr:
    63  		var cond cty.Value
    64  		if val := newLiteralValueExpr(ctx, expr.Condition); val != nil {
    65  			c, diags := val.Value(ctx)
    66  			// allow only bool predicates
    67  			if c.Type() != cty.Bool || diags.HasErrors() {
    68  				return expr
    69  			}
    70  			cond = c
    71  		}
    72  
    73  		// Conditional results must not be cty.NilVal and from same type
    74  		// so evaluate already replaced condition first and return
    75  		// LiteralValueExpr instead of a ConditionalExpr.
    76  		if cond.False() {
    77  			return newLiteralValueExpr(ctx, expr.FalseResult)
    78  		}
    79  		return newLiteralValueExpr(ctx, expr.TrueResult)
    80  	case *hclsyntax.BinaryOpExpr:
    81  		if val := newLiteralValueExpr(ctx, expr.LHS); val != nil {
    82  			expr.LHS = val
    83  		}
    84  		if val := newLiteralValueExpr(ctx, expr.RHS); val != nil {
    85  			expr.RHS = val
    86  		}
    87  		return expr
    88  	case *hclsyntax.ObjectConsKeyExpr:
    89  		if val := newLiteralValueExpr(ctx, expr.Wrapped); val != nil {
    90  			expr.Wrapped = val
    91  		}
    92  		return expr
    93  	case *hclsyntax.ObjectConsExpr:
    94  		for i, item := range expr.Items {
    95  			// KeyExpr can't be NilVal
    96  			for _, v := range item.KeyExpr.Variables() {
    97  				if traversalValue(ctx.Variables, v) == cty.NilVal {
    98  					expr.Items[i].KeyExpr = &hclsyntax.LiteralValueExpr{Val: emptyStringVal, SrcRange: v.SourceRange()}
    99  					break
   100  				}
   101  			}
   102  
   103  			if val := newLiteralValueExpr(ctx, item.ValueExpr); val != nil {
   104  				expr.Items[i].ValueExpr = val
   105  			}
   106  		}
   107  		return expr
   108  	case *hclsyntax.TemplateExpr:
   109  		for p, part := range expr.Parts {
   110  			expr.Parts[p] = newLiteralValueExpr(ctx, part)
   111  		}
   112  
   113  		// "pre"-evaluate to be able to combine string expressions with empty strings on NilVal result.
   114  		c, _ := expr.Value(ctx)
   115  		if c.IsNull() {
   116  			return &hclsyntax.LiteralValueExpr{Val: emptyStringVal, SrcRange: expr.Range()}
   117  		}
   118  		return &hclsyntax.LiteralValueExpr{Val: c, SrcRange: expr.Range()}
   119  	case *hclsyntax.TemplateWrapExpr:
   120  		if val := newLiteralValueExpr(ctx, expr.Wrapped); val != nil {
   121  			expr.Wrapped = val
   122  		}
   123  		return expr
   124  	case *hclsyntax.ScopeTraversalExpr:
   125  		if traversalValue(ctx.Variables, expr.Traversal) == cty.NilVal {
   126  			return &hclsyntax.LiteralValueExpr{Val: cty.NilVal, SrcRange: expr.SrcRange}
   127  		}
   128  		return expr
   129  	case *hclsyntax.FunctionCallExpr:
   130  		for a, arg := range expr.Args {
   131  			expr.Args[a] = newLiteralValueExpr(ctx, arg)
   132  		}
   133  		return expr
   134  	case *hclsyntax.TupleConsExpr:
   135  		for e, ex := range expr.Exprs {
   136  			expr.Exprs[e] = newLiteralValueExpr(ctx, ex)
   137  		}
   138  		return expr
   139  	case *hclsyntax.ForExpr:
   140  		expr.CollExpr = newLiteralValueExpr(ctx, expr.CollExpr)
   141  		return expr
   142  	case *hclsyntax.ParenthesesExpr:
   143  		expr.Expression = newLiteralValueExpr(ctx, expr.Expression)
   144  		return expr
   145  	case *hclsyntax.SplatExpr:
   146  		expr.Each = newLiteralValueExpr(ctx, expr.Each)
   147  		expr.Source = newLiteralValueExpr(ctx, expr.Source)
   148  		return expr
   149  	case *hclsyntax.IndexExpr:
   150  		if val := newLiteralValueExpr(ctx, expr.Collection); val != nil {
   151  			expr.Collection = val
   152  		}
   153  		if val := newLiteralValueExpr(ctx, expr.Key); val != nil {
   154  			expr.Key = val
   155  		}
   156  		return expr
   157  	case *hclsyntax.RelativeTraversalExpr:
   158  		if val := newLiteralValueExpr(ctx, expr.Source); val != nil {
   159  			expr.Source = val
   160  		}
   161  		return expr
   162  	case *hclsyntax.UnaryOpExpr:
   163  		if val := newLiteralValueExpr(ctx, expr.Val); val != nil {
   164  			expr.Val = val
   165  		}
   166  		return expr
   167  	case *hclsyntax.AnonSymbolExpr:
   168  		return expr
   169  	case *hclsyntax.LiteralValueExpr:
   170  		return expr
   171  	default:
   172  		panic("eval.Value: expression type not supported: " + fmt.Sprint(reflect.TypeOf(expr).String()))
   173  	}
   174  }
   175  
   176  func clone(exp hcl.Expression) hclsyntax.Expression {
   177  	switch expr := exp.(type) {
   178  	case *hclsyntax.BinaryOpExpr:
   179  		op := *expr.Op
   180  		ex := &hclsyntax.BinaryOpExpr{
   181  			LHS:      clone(expr.LHS),
   182  			Op:       &op,
   183  			RHS:      clone(expr.RHS),
   184  			SrcRange: expr.SrcRange,
   185  		}
   186  		return ex
   187  	case *hclsyntax.ConditionalExpr:
   188  		ex := &hclsyntax.ConditionalExpr{
   189  			Condition:   clone(expr.Condition),
   190  			FalseResult: clone(expr.FalseResult),
   191  			SrcRange:    expr.SrcRange,
   192  			TrueResult:  clone(expr.TrueResult),
   193  		}
   194  		return ex
   195  	case *hclsyntax.ForExpr:
   196  		ex := *expr
   197  		ex.CollExpr = clone(expr.CollExpr)
   198  		return &ex
   199  	case *hclsyntax.FunctionCallExpr:
   200  		ex := *expr
   201  		ex.Args = make([]hclsyntax.Expression, 0)
   202  		for _, arg := range expr.Args { // just clone args; will be modified, see above
   203  			ex.Args = append(ex.Args, clone(arg))
   204  		}
   205  		return &ex
   206  	case *hclsyntax.ObjectConsExpr:
   207  		ex := *expr
   208  		ex.Items = make([]hclsyntax.ObjectConsItem, len(ex.Items))
   209  		for i, item := range expr.Items {
   210  			ex.Items[i].KeyExpr = clone(item.KeyExpr)
   211  			ex.Items[i].ValueExpr = clone(item.ValueExpr)
   212  		}
   213  		return &ex
   214  	case *hclsyntax.ObjectConsKeyExpr:
   215  		ex := *expr
   216  		ex.Wrapped = clone(ex.Wrapped)
   217  		return &ex
   218  	case *hclsyntax.ParenthesesExpr:
   219  		ex := *expr
   220  		ex.Expression = clone(expr.Expression)
   221  		return &ex
   222  	case *hclsyntax.ScopeTraversalExpr:
   223  		ex := *expr
   224  		return &ex
   225  	case *hclsyntax.TemplateExpr:
   226  		ex := *expr
   227  		ex.Parts = make([]hclsyntax.Expression, len(expr.Parts))
   228  		for i, item := range expr.Parts {
   229  			ex.Parts[i] = clone(item)
   230  		}
   231  		return &ex
   232  	case *hclsyntax.TemplateWrapExpr:
   233  		ex := *expr
   234  		ex.Wrapped = clone(ex.Wrapped)
   235  		return &ex
   236  	case *hclsyntax.LiteralValueExpr:
   237  		ex := *expr
   238  		return &ex
   239  	case *hclsyntax.TupleConsExpr:
   240  		ex := *expr
   241  		ex.Exprs = make([]hclsyntax.Expression, len(expr.Exprs))
   242  		for i, item := range expr.Exprs {
   243  			ex.Exprs[i] = clone(item)
   244  		}
   245  		return &ex
   246  	case *hclsyntax.SplatExpr:
   247  		ex := *expr
   248  		ex.Source = clone(expr.Source)
   249  		return &ex
   250  	case *hclsyntax.IndexExpr:
   251  		ex := &hclsyntax.IndexExpr{
   252  			Collection:   clone(expr.Collection),
   253  			Key:          clone(expr.Key),
   254  			SrcRange:     expr.SrcRange,
   255  			OpenRange:    expr.OpenRange,
   256  			BracketRange: expr.BracketRange,
   257  		}
   258  		return ex
   259  	case *hclsyntax.RelativeTraversalExpr:
   260  		ex := &hclsyntax.RelativeTraversalExpr{
   261  			Source:    clone(expr.Source),
   262  			Traversal: expr.Traversal,
   263  			SrcRange:  expr.SrcRange,
   264  		}
   265  		return ex
   266  	case *hclsyntax.UnaryOpExpr:
   267  		ex := &hclsyntax.UnaryOpExpr{
   268  			Op:          expr.Op,
   269  			Val:         clone(expr.Val),
   270  			SrcRange:    expr.SrcRange,
   271  			SymbolRange: expr.SymbolRange,
   272  		}
   273  		return ex
   274  	case *hclsyntax.AnonSymbolExpr:
   275  		return &hclsyntax.AnonSymbolExpr{SrcRange: expr.SrcRange}
   276  	default:
   277  		panic("eval: unsupported expression: " + reflect.TypeOf(exp).String())
   278  	}
   279  }
   280  
   281  func traversalValue(vars map[string]cty.Value, traversal hcl.Traversal) cty.Value {
   282  	traverseSplit := traversal.SimpleSplit()
   283  	rootTraverse := traverseSplit.Abs[0].(hcl.TraverseRoot) // TODO: check for abs ?
   284  	name := rootTraverse.Name
   285  
   286  	if _, ok := vars[name]; !ok {
   287  		return cty.NilVal
   288  	}
   289  
   290  	return walk(vars[name], cty.NilVal, traverseSplit.Rel)
   291  }
   292  
   293  // walk goes through the given variables path and returns the fallback value if no variable is set.
   294  func walk(variables, fallback cty.Value, traversal hcl.Traversal) cty.Value {
   295  	if len(traversal) == 0 {
   296  		return variables
   297  	}
   298  
   299  	hasNext := len(traversal) > 1
   300  
   301  	currentFn := func(name string) (current cty.Value, exist bool) {
   302  		if variables.Type().IsObjectType() || variables.Type().IsMapType() {
   303  			current, exist = variables.AsValueMap()[name]
   304  		}
   305  		return current, exist
   306  	}
   307  
   308  	switch t := traversal[0].(type) {
   309  	case hcl.TraverseAttr:
   310  		current, exist := currentFn(t.Name)
   311  		if !exist {
   312  			return fallback
   313  		}
   314  		return walk(current, fallback, traversal[1:])
   315  	case hcl.TraverseIndex:
   316  		if !variables.CanIterateElements() {
   317  			return fallback
   318  		}
   319  
   320  		switch t.Key.Type() {
   321  		case cty.Number:
   322  			if variables.HasIndex(t.Key).True() {
   323  				if hasNext {
   324  					fidx := t.Key.AsBigFloat()
   325  					idx, _ := fidx.Int64()
   326  					return walk(variables.AsValueSlice()[idx], fallback, traversal[1:])
   327  				}
   328  				return variables
   329  			}
   330  		case cty.String:
   331  			current, exist := currentFn(t.Key.AsString())
   332  			if !exist {
   333  				return fallback
   334  			}
   335  			if hasNext {
   336  				return walk(current, fallback, traversal[1:])
   337  			}
   338  			return variables
   339  		}
   340  		return fallback
   341  	default:
   342  		panic("eval: unsupported traversal: " + reflect.TypeOf(t).String())
   343  	}
   344  }