aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/zclconf/go-cty/cty/path.go
blob: bf1a7c15aa017eff10017eea9dca77715a8c65ae (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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package cty

import (
	"errors"
	"fmt"
)

// A Path is a sequence of operations to locate a nested value within a
// data structure.
//
// The empty Path represents the given item. Any PathSteps within represent
// taking a single step down into a data structure.
//
// Path has some convenience methods for gradually constructing a path,
// but callers can also feel free to just produce a slice of PathStep manually
// and convert to this type, which may be more appropriate in environments
// where memory pressure is a concern.
//
// Although a Path is technically mutable, by convention callers should not
// mutate a path once it has been built and passed to some other subsystem.
// Instead, use Copy and then mutate the copy before using it.
type Path []PathStep

// PathStep represents a single step down into a data structure, as part
// of a Path. PathStep is a closed interface, meaning that the only
// permitted implementations are those within this package.
type PathStep interface {
	pathStepSigil() pathStepImpl
	Apply(Value) (Value, error)
}

// embed pathImpl into a struct to declare it a PathStep implementation
type pathStepImpl struct{}

func (p pathStepImpl) pathStepSigil() pathStepImpl {
	return p
}

// Index returns a new Path that is the reciever with an IndexStep appended
// to the end.
//
// This is provided as a convenient way to construct paths, but each call
// will create garbage so it should not be used where memory pressure is a
// concern.
func (p Path) Index(v Value) Path {
	ret := make(Path, len(p)+1)
	copy(ret, p)
	ret[len(p)] = IndexStep{
		Key: v,
	}
	return ret
}

// IndexPath is a convenience method to start a new Path with an IndexStep.
func IndexPath(v Value) Path {
	return Path{}.Index(v)
}

// GetAttr returns a new Path that is the reciever with a GetAttrStep appended
// to the end.
//
// This is provided as a convenient way to construct paths, but each call
// will create garbage so it should not be used where memory pressure is a
// concern.
func (p Path) GetAttr(name string) Path {
	ret := make(Path, len(p)+1)
	copy(ret, p)
	ret[len(p)] = GetAttrStep{
		Name: name,
	}
	return ret
}

// GetAttrPath is a convenience method to start a new Path with a GetAttrStep.
func GetAttrPath(name string) Path {
	return Path{}.GetAttr(name)
}

// Apply applies each of the steps in turn to successive values starting with
// the given value, and returns the result. If any step returns an error,
// the whole operation returns an error.
func (p Path) Apply(val Value) (Value, error) {
	var err error
	for i, step := range p {
		val, err = step.Apply(val)
		if err != nil {
			return NilVal, fmt.Errorf("at step %d: %s", i, err)
		}
	}
	return val, nil
}

// LastStep applies the given path up to the last step and then returns
// the resulting value and the final step.
//
// This is useful when dealing with assignment operations, since in that
// case the *value* of the last step is not important (and may not, in fact,
// present at all) and we care only about its location.
//
// Since LastStep applies all steps except the last, it will return errors
// for those steps in the same way as Apply does.
//
// If the path has *no* steps then the returned PathStep will be nil,
// representing that any operation should be applied directly to the
// given value.
func (p Path) LastStep(val Value) (Value, PathStep, error) {
	var err error

	if len(p) == 0 {
		return val, nil, nil
	}

	journey := p[:len(p)-1]
	val, err = journey.Apply(val)
	if err != nil {
		return NilVal, nil, err
	}
	return val, p[len(p)-1], nil
}

// Copy makes a shallow copy of the receiver. Often when paths are passed to
// caller code they come with the constraint that they are valid only until
// the caller returns, due to how they are constructed internally. Callers
// can use Copy to conveniently produce a copy of the value that _they_ control
// the validity of.
func (p Path) Copy() Path {
	ret := make(Path, len(p))
	copy(ret, p)
	return ret
}

// IndexStep is a Step implementation representing applying the index operation
// to a value, which must be of either a list, map, or set type.
//
// When describing a path through a *type* rather than a concrete value,
// the Key may be an unknown value, indicating that the step applies to
// *any* key of the given type.
//
// When indexing into a set, the Key is actually the element being accessed
// itself, since in sets elements are their own identity.
type IndexStep struct {
	pathStepImpl
	Key Value
}

// Apply returns the value resulting from indexing the given value with
// our key value.
func (s IndexStep) Apply(val Value) (Value, error) {
	if val == NilVal || val.IsNull() {
		return NilVal, errors.New("cannot index a null value")
	}

	switch s.Key.Type() {
	case Number:
		if !(val.Type().IsListType() || val.Type().IsTupleType()) {
			return NilVal, errors.New("not a list type")
		}
	case String:
		if !val.Type().IsMapType() {
			return NilVal, errors.New("not a map type")
		}
	default:
		return NilVal, errors.New("key value not number or string")
	}

	has := val.HasIndex(s.Key)
	if !has.IsKnown() {
		return UnknownVal(val.Type().ElementType()), nil
	}
	if !has.True() {
		return NilVal, errors.New("value does not have given index key")
	}

	return val.Index(s.Key), nil
}

func (s IndexStep) GoString() string {
	return fmt.Sprintf("cty.IndexStep{Key:%#v}", s.Key)
}

// GetAttrStep is a Step implementation representing retrieving an attribute
// from a value, which must be of an object type.
type GetAttrStep struct {
	pathStepImpl
	Name string
}

// Apply returns the value of our named attribute from the given value, which
// must be of an object type that has a value of that name.
func (s GetAttrStep) Apply(val Value) (Value, error) {
	if val == NilVal || val.IsNull() {
		return NilVal, errors.New("cannot access attributes on a null value")
	}

	if !val.Type().IsObjectType() {
		return NilVal, errors.New("not an object type")
	}

	if !val.Type().HasAttribute(s.Name) {
		return NilVal, fmt.Errorf("object has no attribute %q", s.Name)
	}

	return val.GetAttr(s.Name), nil
}

func (s GetAttrStep) GoString() string {
	return fmt.Sprintf("cty.GetAttrStep{Name:%q}", s.Name)
}