1package addrs
2
3import (
4	"fmt"
5
6	"github.com/hashicorp/hcl/v2"
7)
8
9// Reference describes a reference to an address with source location
10// information.
11type Reference struct {
12	Subject     Referenceable
13	SourceRange hcl.Range
14	Remaining   hcl.Traversal
15}
16
17// ParseRef attempts to extract a referencable address from the prefix of the
18// given traversal, which must be an absolute traversal or this function
19// will panic.
20//
21// If no error diagnostics are returned, the returned reference includes the
22// address that was extracted, the source range it was extracted from, and any
23// remaining relative traversal that was not consumed as part of the
24// reference.
25//
26// If error diagnostics are returned then the Reference value is invalid and
27// must not be used.
28func ParseRef(traversal hcl.Traversal) (*Reference, hcl.Diagnostics) {
29	ref, diags := parseRef(traversal)
30
31	// Normalize a little to make life easier for callers.
32	if ref != nil {
33		if len(ref.Remaining) == 0 {
34			ref.Remaining = nil
35		}
36	}
37
38	return ref, diags
39}
40
41func parseRef(traversal hcl.Traversal) (*Reference, hcl.Diagnostics) {
42	var diags hcl.Diagnostics
43
44	root := traversal.RootName()
45	rootRange := traversal[0].SourceRange()
46
47	switch root {
48
49	case "var":
50		name, rng, remain, diags := parseSingleAttrRef(traversal)
51		return &Reference{
52			Subject:     InputVariable{Name: name},
53			SourceRange: rng,
54			Remaining:   remain,
55		}, diags
56
57	default:
58		diags = append(diags, &hcl.Diagnostic{
59			Severity: hcl.DiagError,
60			Summary:  "Unhandled reference type",
61			Detail:   `Currently parseRef can only parse "var" references.`,
62			Subject:  &rootRange,
63		})
64	}
65	return nil, diags
66}
67
68func parseSingleAttrRef(traversal hcl.Traversal) (string, hcl.Range, hcl.Traversal, hcl.Diagnostics) {
69	var diags hcl.Diagnostics
70
71	root := traversal.RootName()
72	rootRange := traversal[0].SourceRange()
73
74	if len(traversal) < 2 {
75		diags = append(diags, &hcl.Diagnostic{
76			Severity: hcl.DiagError,
77			Summary:  "Invalid reference",
78			Detail:   fmt.Sprintf("The %q object cannot be accessed directly. Instead, access one of its attributes.", root),
79			Subject:  &rootRange,
80		})
81		return "", hcl.Range{}, nil, diags
82	}
83	if attrTrav, ok := traversal[1].(hcl.TraverseAttr); ok {
84		return attrTrav.Name, hcl.RangeBetween(rootRange, attrTrav.SrcRange), traversal[2:], diags
85	}
86	diags = diags.Append(&hcl.Diagnostic{
87		Severity: hcl.DiagError,
88		Summary:  "Invalid reference",
89		Detail:   fmt.Sprintf("The %q object does not support this operation.", root),
90		Subject:  traversal[1].SourceRange().Ptr(),
91	})
92	return "", hcl.Range{}, nil, diags
93}
94