hcl: ExprAsKeyword function
A common pattern is emerging in calling applications of using single-item absolute traversals to give the impression of static language keywords. This new function makes that explicitly possible and allows a convenient pattern for doing so that should improve the readability of a calling application making use of it.
This commit is contained in:
parent
9f91684a1f
commit
102e698035
@ -60,3 +60,62 @@ func RelTraversalForExpr(expr Expression) (Traversal, Diagnostics) {
|
||||
}
|
||||
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 ""
|
||||
}
|
||||
|
@ -9,6 +9,12 @@ type asTraversalSupported struct {
|
||||
RootName string
|
||||
}
|
||||
|
||||
type asTraversalSupportedAttr struct {
|
||||
staticExpr
|
||||
RootName string
|
||||
AttrName string
|
||||
}
|
||||
|
||||
type asTraversalNotSupported struct {
|
||||
staticExpr
|
||||
}
|
||||
@ -30,6 +36,17 @@ func (e asTraversalSupported) AsTraversal() Traversal {
|
||||
}
|
||||
}
|
||||
|
||||
func (e asTraversalSupportedAttr) AsTraversal() Traversal {
|
||||
return Traversal{
|
||||
TraverseRoot{
|
||||
Name: e.RootName,
|
||||
},
|
||||
TraverseAttr{
|
||||
Name: e.AttrName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (e asTraversalDeclined) AsTraversal() Traversal {
|
||||
return nil
|
||||
}
|
||||
@ -149,3 +166,53 @@ func TestRelTraversalForExpr(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExprAsKeyword(t *testing.T) {
|
||||
tests := []struct {
|
||||
Expr Expression
|
||||
Want string
|
||||
}{
|
||||
{
|
||||
asTraversalSupported{RootName: "foo"},
|
||||
"foo",
|
||||
},
|
||||
{
|
||||
asTraversalSupportedAttr{
|
||||
RootName: "foo",
|
||||
AttrName: "bar",
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
asTraversalNotSupported{},
|
||||
"",
|
||||
},
|
||||
{
|
||||
asTraversalDeclined{},
|
||||
"",
|
||||
},
|
||||
{
|
||||
asTraversalWrappedDelegated{
|
||||
original: asTraversalSupported{RootName: "foo"},
|
||||
},
|
||||
"foo",
|
||||
},
|
||||
{
|
||||
asTraversalWrappedDelegated{
|
||||
original: asTraversalWrappedDelegated{
|
||||
original: asTraversalSupported{RootName: "foo"},
|
||||
},
|
||||
},
|
||||
"foo",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
got := ExprAsKeyword(test.Expr)
|
||||
if got != test.Want {
|
||||
t.Errorf("wrong result %q; want %q\ninput: %T", got, test.Want, test.Expr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user