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  }