github.com/hashicorp/hcl/v2@v2.20.0/traversal_for_expr.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hcl 5 6 // AbsTraversalForExpr attempts to interpret the given expression as 7 // an absolute traversal, or returns error diagnostic(s) if that is 8 // not possible for the given expression. 9 // 10 // A particular Expression implementation can support this function by 11 // offering a method called AsTraversal that takes no arguments and 12 // returns either a valid absolute traversal or nil to indicate that 13 // no traversal is possible. Alternatively, an implementation can support 14 // UnwrapExpression to delegate handling of this function to a wrapped 15 // Expression object. 16 // 17 // In most cases the calling application is interested in the value 18 // that results from an expression, but in rarer cases the application 19 // needs to see the name of the variable and subsequent 20 // attributes/indexes itself, for example to allow users to give references 21 // to the variables themselves rather than to their values. An implementer 22 // of this function should at least support attribute and index steps. 23 func AbsTraversalForExpr(expr Expression) (Traversal, Diagnostics) { 24 type asTraversal interface { 25 AsTraversal() Traversal 26 } 27 28 physExpr := UnwrapExpressionUntil(expr, func(expr Expression) bool { 29 _, supported := expr.(asTraversal) 30 return supported 31 }) 32 33 if asT, supported := physExpr.(asTraversal); supported { 34 if traversal := asT.AsTraversal(); traversal != nil { 35 return traversal, nil 36 } 37 } 38 return nil, Diagnostics{ 39 &Diagnostic{ 40 Severity: DiagError, 41 Summary: "Invalid expression", 42 Detail: "A single static variable reference is required: only attribute access and indexing with constant keys. No calculations, function calls, template expressions, etc are allowed here.", 43 Subject: expr.Range().Ptr(), 44 }, 45 } 46 } 47 48 // RelTraversalForExpr is similar to AbsTraversalForExpr but it returns 49 // a relative traversal instead. Due to the nature of HCL expressions, the 50 // first element of the returned traversal is always a TraverseAttr, and 51 // then it will be followed by zero or more other expressions. 52 // 53 // Any expression accepted by AbsTraversalForExpr is also accepted by 54 // RelTraversalForExpr. 55 func RelTraversalForExpr(expr Expression) (Traversal, Diagnostics) { 56 traversal, diags := AbsTraversalForExpr(expr) 57 if len(traversal) > 0 { 58 ret := make(Traversal, len(traversal)) 59 copy(ret, traversal) 60 root := traversal[0].(TraverseRoot) 61 ret[0] = TraverseAttr{ 62 Name: root.Name, 63 SrcRange: root.SrcRange, 64 } 65 return ret, diags 66 } 67 return traversal, diags 68 } 69 70 // ExprAsKeyword attempts to interpret the given expression as a static keyword, 71 // returning the keyword string if possible, and the empty string if not. 72 // 73 // A static keyword, for the sake of this function, is a single identifier. 74 // For example, the following attribute has an expression that would produce 75 // the keyword "foo": 76 // 77 // example = foo 78 // 79 // This function is a variant of AbsTraversalForExpr, which uses the same 80 // interface on the given expression. This helper constrains the result 81 // further by requiring only a single root identifier. 82 // 83 // This function is intended to be used with the following idiom, to recognize 84 // situations where one of a fixed set of keywords is required and arbitrary 85 // expressions are not allowed: 86 // 87 // switch hcl.ExprAsKeyword(expr) { 88 // case "allow": 89 // // (take suitable action for keyword "allow") 90 // case "deny": 91 // // (take suitable action for keyword "deny") 92 // default: 93 // diags = append(diags, &hcl.Diagnostic{ 94 // // ... "invalid keyword" diagnostic message ... 95 // }) 96 // } 97 // 98 // The above approach will generate the same message for both the use of an 99 // unrecognized keyword and for not using a keyword at all, which is usually 100 // reasonable if the message specifies that the given value must be a keyword 101 // from that fixed list. 102 // 103 // Note that in the native syntax the keywords "true", "false", and "null" are 104 // recognized as literal values during parsing and so these reserved words 105 // cannot not be accepted as keywords by this function. 106 // 107 // Since interpreting an expression as a keyword bypasses usual expression 108 // evaluation, it should be used sparingly for situations where e.g. one of 109 // a fixed set of keywords is used in a structural way in a special attribute 110 // to affect the further processing of a block. 111 func ExprAsKeyword(expr Expression) string { 112 type asTraversal interface { 113 AsTraversal() Traversal 114 } 115 116 physExpr := UnwrapExpressionUntil(expr, func(expr Expression) bool { 117 _, supported := expr.(asTraversal) 118 return supported 119 }) 120 121 if asT, supported := physExpr.(asTraversal); supported { 122 if traversal := asT.AsTraversal(); len(traversal) == 1 { 123 return traversal.RootName() 124 } 125 } 126 return "" 127 }