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 }