github.com/hashicorp/hcl/v2@v2.20.0/traversal.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package hcl
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/zclconf/go-cty/cty"
    10  )
    11  
    12  // A Traversal is a description of traversing through a value through a
    13  // series of operations such as attribute lookup, index lookup, etc.
    14  //
    15  // It is used to look up values in scopes, for example.
    16  //
    17  // The traversal operations are implementations of interface Traverser.
    18  // This is a closed set of implementations, so the interface cannot be
    19  // implemented from outside this package.
    20  //
    21  // A traversal can be absolute (its first value is a symbol name) or relative
    22  // (starts from an existing value).
    23  type Traversal []Traverser
    24  
    25  // TraversalJoin appends a relative traversal to an absolute traversal to
    26  // produce a new absolute traversal.
    27  func TraversalJoin(abs Traversal, rel Traversal) Traversal {
    28  	if abs.IsRelative() {
    29  		panic("first argument to TraversalJoin must be absolute")
    30  	}
    31  	if !rel.IsRelative() {
    32  		panic("second argument to TraversalJoin must be relative")
    33  	}
    34  
    35  	ret := make(Traversal, len(abs)+len(rel))
    36  	copy(ret, abs)
    37  	copy(ret[len(abs):], rel)
    38  	return ret
    39  }
    40  
    41  // TraverseRel applies the receiving traversal to the given value, returning
    42  // the resulting value. This is supported only for relative traversals,
    43  // and will panic if applied to an absolute traversal.
    44  func (t Traversal) TraverseRel(val cty.Value) (cty.Value, Diagnostics) {
    45  	if !t.IsRelative() {
    46  		panic("can't use TraverseRel on an absolute traversal")
    47  	}
    48  
    49  	current := val
    50  	var diags Diagnostics
    51  	for _, tr := range t {
    52  		var newDiags Diagnostics
    53  		current, newDiags = tr.TraversalStep(current)
    54  		diags = append(diags, newDiags...)
    55  		if newDiags.HasErrors() {
    56  			return cty.DynamicVal, diags
    57  		}
    58  	}
    59  	return current, diags
    60  }
    61  
    62  // TraverseAbs applies the receiving traversal to the given eval context,
    63  // returning the resulting value. This is supported only for absolute
    64  // traversals, and will panic if applied to a relative traversal.
    65  func (t Traversal) TraverseAbs(ctx *EvalContext) (cty.Value, Diagnostics) {
    66  	if t.IsRelative() {
    67  		panic("can't use TraverseAbs on a relative traversal")
    68  	}
    69  
    70  	split := t.SimpleSplit()
    71  	root := split.Abs[0].(TraverseRoot)
    72  	name := root.Name
    73  
    74  	thisCtx := ctx
    75  	hasNonNil := false
    76  	for thisCtx != nil {
    77  		if thisCtx.Variables == nil {
    78  			thisCtx = thisCtx.parent
    79  			continue
    80  		}
    81  		hasNonNil = true
    82  		val, exists := thisCtx.Variables[name]
    83  		if exists {
    84  			return split.Rel.TraverseRel(val)
    85  		}
    86  		thisCtx = thisCtx.parent
    87  	}
    88  
    89  	if !hasNonNil {
    90  		return cty.DynamicVal, Diagnostics{
    91  			{
    92  				Severity: DiagError,
    93  				Summary:  "Variables not allowed",
    94  				Detail:   "Variables may not be used here.",
    95  				Subject:  &root.SrcRange,
    96  			},
    97  		}
    98  	}
    99  
   100  	suggestions := make([]string, 0, len(ctx.Variables))
   101  	thisCtx = ctx
   102  	for thisCtx != nil {
   103  		for k := range thisCtx.Variables {
   104  			suggestions = append(suggestions, k)
   105  		}
   106  		thisCtx = thisCtx.parent
   107  	}
   108  	suggestion := nameSuggestion(name, suggestions)
   109  	if suggestion != "" {
   110  		suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
   111  	}
   112  
   113  	return cty.DynamicVal, Diagnostics{
   114  		{
   115  			Severity: DiagError,
   116  			Summary:  "Unknown variable",
   117  			Detail:   fmt.Sprintf("There is no variable named %q.%s", name, suggestion),
   118  			Subject:  &root.SrcRange,
   119  		},
   120  	}
   121  }
   122  
   123  // IsRelative returns true if the receiver is a relative traversal, or false
   124  // otherwise.
   125  func (t Traversal) IsRelative() bool {
   126  	if len(t) == 0 {
   127  		return true
   128  	}
   129  	if _, firstIsRoot := t[0].(TraverseRoot); firstIsRoot {
   130  		return false
   131  	}
   132  	return true
   133  }
   134  
   135  // SimpleSplit returns a TraversalSplit where the name lookup is the absolute
   136  // part and the remainder is the relative part. Supported only for
   137  // absolute traversals, and will panic if applied to a relative traversal.
   138  //
   139  // This can be used by applications that have a relatively-simple variable
   140  // namespace where only the top-level is directly populated in the scope, with
   141  // everything else handled by relative lookups from those initial values.
   142  func (t Traversal) SimpleSplit() TraversalSplit {
   143  	if t.IsRelative() {
   144  		panic("can't use SimpleSplit on a relative traversal")
   145  	}
   146  	return TraversalSplit{
   147  		Abs: t[0:1],
   148  		Rel: t[1:],
   149  	}
   150  }
   151  
   152  // RootName returns the root name for a absolute traversal. Will panic if
   153  // called on a relative traversal.
   154  func (t Traversal) RootName() string {
   155  	if t.IsRelative() {
   156  		panic("can't use RootName on a relative traversal")
   157  
   158  	}
   159  	return t[0].(TraverseRoot).Name
   160  }
   161  
   162  // SourceRange returns the source range for the traversal.
   163  func (t Traversal) SourceRange() Range {
   164  	if len(t) == 0 {
   165  		// Nothing useful to return here, but we'll return something
   166  		// that's correctly-typed at least.
   167  		return Range{}
   168  	}
   169  
   170  	return RangeBetween(t[0].SourceRange(), t[len(t)-1].SourceRange())
   171  }
   172  
   173  // TraversalSplit represents a pair of traversals, the first of which is
   174  // an absolute traversal and the second of which is relative to the first.
   175  //
   176  // This is used by calling applications that only populate prefixes of the
   177  // traversals in the scope, with Abs representing the part coming from the
   178  // scope and Rel representing the remaining steps once that part is
   179  // retrieved.
   180  type TraversalSplit struct {
   181  	Abs Traversal
   182  	Rel Traversal
   183  }
   184  
   185  // TraverseAbs traverses from a scope to the value resulting from the
   186  // absolute traversal.
   187  func (t TraversalSplit) TraverseAbs(ctx *EvalContext) (cty.Value, Diagnostics) {
   188  	return t.Abs.TraverseAbs(ctx)
   189  }
   190  
   191  // TraverseRel traverses from a given value, assumed to be the result of
   192  // TraverseAbs on some scope, to a final result for the entire split traversal.
   193  func (t TraversalSplit) TraverseRel(val cty.Value) (cty.Value, Diagnostics) {
   194  	return t.Rel.TraverseRel(val)
   195  }
   196  
   197  // Traverse is a convenience function to apply TraverseAbs followed by
   198  // TraverseRel.
   199  func (t TraversalSplit) Traverse(ctx *EvalContext) (cty.Value, Diagnostics) {
   200  	v1, diags := t.TraverseAbs(ctx)
   201  	if diags.HasErrors() {
   202  		return cty.DynamicVal, diags
   203  	}
   204  	v2, newDiags := t.TraverseRel(v1)
   205  	diags = append(diags, newDiags...)
   206  	return v2, diags
   207  }
   208  
   209  // Join concatenates together the Abs and Rel parts to produce a single
   210  // absolute traversal.
   211  func (t TraversalSplit) Join() Traversal {
   212  	return TraversalJoin(t.Abs, t.Rel)
   213  }
   214  
   215  // RootName returns the root name for the absolute part of the split.
   216  func (t TraversalSplit) RootName() string {
   217  	return t.Abs.RootName()
   218  }
   219  
   220  // A Traverser is a step within a Traversal.
   221  type Traverser interface {
   222  	TraversalStep(cty.Value) (cty.Value, Diagnostics)
   223  	SourceRange() Range
   224  	isTraverserSigil() isTraverser
   225  }
   226  
   227  // Embed this in a struct to declare it as a Traverser
   228  type isTraverser struct {
   229  }
   230  
   231  func (tr isTraverser) isTraverserSigil() isTraverser {
   232  	return isTraverser{}
   233  }
   234  
   235  // TraverseRoot looks up a root name in a scope. It is used as the first step
   236  // of an absolute Traversal, and cannot itself be traversed directly.
   237  type TraverseRoot struct {
   238  	isTraverser
   239  	Name     string
   240  	SrcRange Range
   241  }
   242  
   243  // TraversalStep on a TraverseName immediately panics, because absolute
   244  // traversals cannot be directly traversed.
   245  func (tn TraverseRoot) TraversalStep(cty.Value) (cty.Value, Diagnostics) {
   246  	panic("Cannot traverse an absolute traversal")
   247  }
   248  
   249  func (tn TraverseRoot) SourceRange() Range {
   250  	return tn.SrcRange
   251  }
   252  
   253  // TraverseAttr looks up an attribute in its initial value.
   254  type TraverseAttr struct {
   255  	isTraverser
   256  	Name     string
   257  	SrcRange Range
   258  }
   259  
   260  func (tn TraverseAttr) TraversalStep(val cty.Value) (cty.Value, Diagnostics) {
   261  	return GetAttr(val, tn.Name, &tn.SrcRange)
   262  }
   263  
   264  func (tn TraverseAttr) SourceRange() Range {
   265  	return tn.SrcRange
   266  }
   267  
   268  // TraverseIndex applies the index operation to its initial value.
   269  type TraverseIndex struct {
   270  	isTraverser
   271  	Key      cty.Value
   272  	SrcRange Range
   273  }
   274  
   275  func (tn TraverseIndex) TraversalStep(val cty.Value) (cty.Value, Diagnostics) {
   276  	return Index(val, tn.Key, &tn.SrcRange)
   277  }
   278  
   279  func (tn TraverseIndex) SourceRange() Range {
   280  	return tn.SrcRange
   281  }
   282  
   283  // TraverseSplat applies the splat operation to its initial value.
   284  type TraverseSplat struct {
   285  	isTraverser
   286  	Each     Traversal
   287  	SrcRange Range
   288  }
   289  
   290  func (tn TraverseSplat) TraversalStep(val cty.Value) (cty.Value, Diagnostics) {
   291  	panic("TraverseSplat not yet implemented")
   292  }
   293  
   294  func (tn TraverseSplat) SourceRange() Range {
   295  	return tn.SrcRange
   296  }