aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/hcl2/hcl/traversal_for_expr.go
blob: f69d5fe9b287aa8571a71a36862cdec149e90976 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package hcl

// AbsTraversalForExpr attempts to interpret the given expression as
// an absolute traversal, or returns error diagnostic(s) if that is
// not possible for the given expression.
//
// A particular Expression implementation can support this function by
// offering a method called AsTraversal that takes no arguments and
// returns either a valid absolute traversal or nil to indicate that
// no traversal is possible. Alternatively, an implementation can support
// UnwrapExpression to delegate handling of this function to a wrapped
// Expression object.
//
// In most cases the calling application is interested in the value
// that results from an expression, but in rarer cases the application
// needs to see the the name of the variable and subsequent
// attributes/indexes itself, for example to allow users to give references
// to the variables themselves rather than to their values. An implementer
// of this function should at least support attribute and index steps.
func AbsTraversalForExpr(expr Expression) (Traversal, Diagnostics) {
	type asTraversal interface {
		AsTraversal() Traversal
	}

	physExpr := UnwrapExpressionUntil(expr, func(expr Expression) bool {
		_, supported := expr.(asTraversal)
		return supported
	})

	if asT, supported := physExpr.(asTraversal); supported {
		if traversal := asT.AsTraversal(); traversal != nil {
			return traversal, nil
		}
	}
	return nil, Diagnostics{
		&Diagnostic{
			Severity: DiagError,
			Summary:  "Invalid expression",
			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.",
			Subject:  expr.Range().Ptr(),
		},
	}
}

// RelTraversalForExpr is similar to AbsTraversalForExpr but it returns
// a relative traversal instead. Due to the nature of HCL expressions, the
// first element of the returned traversal is always a TraverseAttr, and
// then it will be followed by zero or more other expressions.
//
// Any expression accepted by AbsTraversalForExpr is also accepted by
// RelTraversalForExpr.
func RelTraversalForExpr(expr Expression) (Traversal, Diagnostics) {
	traversal, diags := AbsTraversalForExpr(expr)
	if len(traversal) > 0 {
		ret := make(Traversal, len(traversal))
		copy(ret, traversal)
		root := traversal[0].(TraverseRoot)
		ret[0] = TraverseAttr{
			Name:     root.Name,
			SrcRange: root.SrcRange,
		}
		return ret, diags
	}
	return traversal, diags
}

// ExprAsKeyword attempts to interpret the given expression as a static keyword,
// returning the keyword string if possible, and the empty string if not.
//
// A static keyword, for the sake of this function, is a single identifier.
// For example, the following attribute has an expression that would produce
// the keyword "foo":
//
//     example = foo
//
// This function is a variant of AbsTraversalForExpr, which uses the same
// interface on the given expression. This helper constrains the result
// further by requiring only a single root identifier.
//
// This function is intended to be used with the following idiom, to recognize
// situations where one of a fixed set of keywords is required and arbitrary
// expressions are not allowed:
//
//     switch hcl.ExprAsKeyword(expr) {
//     case "allow":
//         // (take suitable action for keyword "allow")
//     case "deny":
//         // (take suitable action for keyword "deny")
//     default:
//         diags = append(diags, &hcl.Diagnostic{
//             // ... "invalid keyword" diagnostic message ...
//         })
//     }
//
// The above approach will generate the same message for both the use of an
// unrecognized keyword and for not using a keyword at all, which is usually
// reasonable if the message specifies that the given value must be a keyword
// from that fixed list.
//
// Note that in the native syntax the keywords "true", "false", and "null" are
// recognized as literal values during parsing and so these reserved words
// cannot not be accepted as keywords by this function.
//
// Since interpreting an expression as a keyword bypasses usual expression
// evaluation, it should be used sparingly for situations where e.g. one of
// a fixed set of keywords is used in a structural way in a special attribute
// to affect the further processing of a block.
func ExprAsKeyword(expr Expression) string {
	type asTraversal interface {
		AsTraversal() Traversal
	}

	physExpr := UnwrapExpressionUntil(expr, func(expr Expression) bool {
		_, supported := expr.(asTraversal)
		return supported
	})

	if asT, supported := physExpr.(asTraversal); supported {
		if traversal := asT.AsTraversal(); len(traversal) == 1 {
			return traversal.RootName()
		}
	}
	return ""
}