]>
Commit | Line | Data |
---|---|---|
15c0b25d AP |
1 | package cty |
2 | ||
3 | import ( | |
4 | "errors" | |
5 | "fmt" | |
6 | ) | |
7 | ||
8 | // A Path is a sequence of operations to locate a nested value within a | |
9 | // data structure. | |
10 | // | |
11 | // The empty Path represents the given item. Any PathSteps within represent | |
12 | // taking a single step down into a data structure. | |
13 | // | |
14 | // Path has some convenience methods for gradually constructing a path, | |
15 | // but callers can also feel free to just produce a slice of PathStep manually | |
16 | // and convert to this type, which may be more appropriate in environments | |
17 | // where memory pressure is a concern. | |
107c1cdb ND |
18 | // |
19 | // Although a Path is technically mutable, by convention callers should not | |
20 | // mutate a path once it has been built and passed to some other subsystem. | |
21 | // Instead, use Copy and then mutate the copy before using it. | |
15c0b25d AP |
22 | type Path []PathStep |
23 | ||
24 | // PathStep represents a single step down into a data structure, as part | |
25 | // of a Path. PathStep is a closed interface, meaning that the only | |
26 | // permitted implementations are those within this package. | |
27 | type PathStep interface { | |
28 | pathStepSigil() pathStepImpl | |
29 | Apply(Value) (Value, error) | |
30 | } | |
31 | ||
32 | // embed pathImpl into a struct to declare it a PathStep implementation | |
33 | type pathStepImpl struct{} | |
34 | ||
35 | func (p pathStepImpl) pathStepSigil() pathStepImpl { | |
36 | return p | |
37 | } | |
38 | ||
39 | // Index returns a new Path that is the reciever with an IndexStep appended | |
40 | // to the end. | |
41 | // | |
42 | // This is provided as a convenient way to construct paths, but each call | |
43 | // will create garbage so it should not be used where memory pressure is a | |
44 | // concern. | |
45 | func (p Path) Index(v Value) Path { | |
46 | ret := make(Path, len(p)+1) | |
47 | copy(ret, p) | |
48 | ret[len(p)] = IndexStep{ | |
49 | Key: v, | |
50 | } | |
51 | return ret | |
52 | } | |
53 | ||
107c1cdb ND |
54 | // IndexPath is a convenience method to start a new Path with an IndexStep. |
55 | func IndexPath(v Value) Path { | |
56 | return Path{}.Index(v) | |
57 | } | |
58 | ||
15c0b25d AP |
59 | // GetAttr returns a new Path that is the reciever with a GetAttrStep appended |
60 | // to the end. | |
61 | // | |
62 | // This is provided as a convenient way to construct paths, but each call | |
63 | // will create garbage so it should not be used where memory pressure is a | |
64 | // concern. | |
65 | func (p Path) GetAttr(name string) Path { | |
66 | ret := make(Path, len(p)+1) | |
67 | copy(ret, p) | |
68 | ret[len(p)] = GetAttrStep{ | |
69 | Name: name, | |
70 | } | |
71 | return ret | |
72 | } | |
73 | ||
863486a6 AG |
74 | // Equals compares 2 Paths for exact equality. |
75 | func (p Path) Equals(other Path) bool { | |
76 | if len(p) != len(other) { | |
77 | return false | |
78 | } | |
79 | ||
80 | for i := range p { | |
81 | pv := p[i] | |
82 | switch pv := pv.(type) { | |
83 | case GetAttrStep: | |
84 | ov, ok := other[i].(GetAttrStep) | |
85 | if !ok || pv != ov { | |
86 | return false | |
87 | } | |
88 | case IndexStep: | |
89 | ov, ok := other[i].(IndexStep) | |
90 | if !ok { | |
91 | return false | |
92 | } | |
93 | ||
94 | if !pv.Key.RawEquals(ov.Key) { | |
95 | return false | |
96 | } | |
97 | default: | |
98 | // Any invalid steps default to evaluating false. | |
99 | return false | |
100 | } | |
101 | } | |
102 | ||
103 | return true | |
104 | ||
105 | } | |
106 | ||
107 | // HasPrefix determines if the path p contains the provided prefix. | |
108 | func (p Path) HasPrefix(prefix Path) bool { | |
109 | if len(prefix) > len(p) { | |
110 | return false | |
111 | } | |
112 | ||
113 | return p[:len(prefix)].Equals(prefix) | |
114 | } | |
115 | ||
107c1cdb ND |
116 | // GetAttrPath is a convenience method to start a new Path with a GetAttrStep. |
117 | func GetAttrPath(name string) Path { | |
118 | return Path{}.GetAttr(name) | |
119 | } | |
120 | ||
15c0b25d AP |
121 | // Apply applies each of the steps in turn to successive values starting with |
122 | // the given value, and returns the result. If any step returns an error, | |
123 | // the whole operation returns an error. | |
124 | func (p Path) Apply(val Value) (Value, error) { | |
125 | var err error | |
126 | for i, step := range p { | |
127 | val, err = step.Apply(val) | |
128 | if err != nil { | |
129 | return NilVal, fmt.Errorf("at step %d: %s", i, err) | |
130 | } | |
131 | } | |
132 | return val, nil | |
133 | } | |
134 | ||
135 | // LastStep applies the given path up to the last step and then returns | |
136 | // the resulting value and the final step. | |
137 | // | |
138 | // This is useful when dealing with assignment operations, since in that | |
139 | // case the *value* of the last step is not important (and may not, in fact, | |
140 | // present at all) and we care only about its location. | |
141 | // | |
142 | // Since LastStep applies all steps except the last, it will return errors | |
143 | // for those steps in the same way as Apply does. | |
144 | // | |
145 | // If the path has *no* steps then the returned PathStep will be nil, | |
146 | // representing that any operation should be applied directly to the | |
147 | // given value. | |
148 | func (p Path) LastStep(val Value) (Value, PathStep, error) { | |
149 | var err error | |
150 | ||
151 | if len(p) == 0 { | |
152 | return val, nil, nil | |
153 | } | |
154 | ||
155 | journey := p[:len(p)-1] | |
156 | val, err = journey.Apply(val) | |
157 | if err != nil { | |
158 | return NilVal, nil, err | |
159 | } | |
160 | return val, p[len(p)-1], nil | |
161 | } | |
162 | ||
163 | // Copy makes a shallow copy of the receiver. Often when paths are passed to | |
164 | // caller code they come with the constraint that they are valid only until | |
165 | // the caller returns, due to how they are constructed internally. Callers | |
166 | // can use Copy to conveniently produce a copy of the value that _they_ control | |
167 | // the validity of. | |
168 | func (p Path) Copy() Path { | |
169 | ret := make(Path, len(p)) | |
170 | copy(ret, p) | |
171 | return ret | |
172 | } | |
173 | ||
174 | // IndexStep is a Step implementation representing applying the index operation | |
175 | // to a value, which must be of either a list, map, or set type. | |
176 | // | |
177 | // When describing a path through a *type* rather than a concrete value, | |
178 | // the Key may be an unknown value, indicating that the step applies to | |
179 | // *any* key of the given type. | |
180 | // | |
181 | // When indexing into a set, the Key is actually the element being accessed | |
182 | // itself, since in sets elements are their own identity. | |
183 | type IndexStep struct { | |
184 | pathStepImpl | |
185 | Key Value | |
186 | } | |
187 | ||
188 | // Apply returns the value resulting from indexing the given value with | |
189 | // our key value. | |
190 | func (s IndexStep) Apply(val Value) (Value, error) { | |
107c1cdb ND |
191 | if val == NilVal || val.IsNull() { |
192 | return NilVal, errors.New("cannot index a null value") | |
193 | } | |
194 | ||
15c0b25d AP |
195 | switch s.Key.Type() { |
196 | case Number: | |
107c1cdb | 197 | if !(val.Type().IsListType() || val.Type().IsTupleType()) { |
15c0b25d AP |
198 | return NilVal, errors.New("not a list type") |
199 | } | |
200 | case String: | |
201 | if !val.Type().IsMapType() { | |
202 | return NilVal, errors.New("not a map type") | |
203 | } | |
204 | default: | |
205 | return NilVal, errors.New("key value not number or string") | |
206 | } | |
207 | ||
208 | has := val.HasIndex(s.Key) | |
209 | if !has.IsKnown() { | |
210 | return UnknownVal(val.Type().ElementType()), nil | |
211 | } | |
212 | if !has.True() { | |
213 | return NilVal, errors.New("value does not have given index key") | |
214 | } | |
215 | ||
216 | return val.Index(s.Key), nil | |
217 | } | |
218 | ||
219 | func (s IndexStep) GoString() string { | |
220 | return fmt.Sprintf("cty.IndexStep{Key:%#v}", s.Key) | |
221 | } | |
222 | ||
223 | // GetAttrStep is a Step implementation representing retrieving an attribute | |
224 | // from a value, which must be of an object type. | |
225 | type GetAttrStep struct { | |
226 | pathStepImpl | |
227 | Name string | |
228 | } | |
229 | ||
230 | // Apply returns the value of our named attribute from the given value, which | |
231 | // must be of an object type that has a value of that name. | |
232 | func (s GetAttrStep) Apply(val Value) (Value, error) { | |
107c1cdb ND |
233 | if val == NilVal || val.IsNull() { |
234 | return NilVal, errors.New("cannot access attributes on a null value") | |
235 | } | |
236 | ||
15c0b25d AP |
237 | if !val.Type().IsObjectType() { |
238 | return NilVal, errors.New("not an object type") | |
239 | } | |
240 | ||
241 | if !val.Type().HasAttribute(s.Name) { | |
242 | return NilVal, fmt.Errorf("object has no attribute %q", s.Name) | |
243 | } | |
244 | ||
245 | return val.GetAttr(s.Name), nil | |
246 | } | |
247 | ||
248 | func (s GetAttrStep) GoString() string { | |
249 | return fmt.Sprintf("cty.GetAttrStep{Name:%q}", s.Name) | |
250 | } |