]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/hcl2/hcl/ops.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / hcl2 / hcl / ops.go
1 package hcl
2
3 import (
4 "fmt"
5 "math/big"
6
7 "github.com/zclconf/go-cty/cty"
8 "github.com/zclconf/go-cty/cty/convert"
9 )
10
11 // Index is a helper function that performs the same operation as the index
12 // operator in the HCL expression language. That is, the result is the
13 // same as it would be for collection[key] in a configuration expression.
14 //
15 // This is exported so that applications can perform indexing in a manner
16 // consistent with how the language does it, including handling of null and
17 // unknown values, etc.
18 //
19 // Diagnostics are produced if the given combination of values is not valid.
20 // Therefore a pointer to a source range must be provided to use in diagnostics,
21 // though nil can be provided if the calling application is going to
22 // ignore the subject of the returned diagnostics anyway.
23 func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics) {
24 if collection.IsNull() {
25 return cty.DynamicVal, Diagnostics{
26 {
27 Severity: DiagError,
28 Summary: "Attempt to index null value",
29 Detail: "This value is null, so it does not have any indices.",
30 Subject: srcRange,
31 },
32 }
33 }
34 if key.IsNull() {
35 return cty.DynamicVal, Diagnostics{
36 {
37 Severity: DiagError,
38 Summary: "Invalid index",
39 Detail: "Can't use a null value as an indexing key.",
40 Subject: srcRange,
41 },
42 }
43 }
44 ty := collection.Type()
45 kty := key.Type()
46 if kty == cty.DynamicPseudoType || ty == cty.DynamicPseudoType {
47 return cty.DynamicVal, nil
48 }
49
50 switch {
51
52 case ty.IsListType() || ty.IsTupleType() || ty.IsMapType():
53 var wantType cty.Type
54 switch {
55 case ty.IsListType() || ty.IsTupleType():
56 wantType = cty.Number
57 case ty.IsMapType():
58 wantType = cty.String
59 default:
60 // should never happen
61 panic("don't know what key type we want")
62 }
63
64 key, keyErr := convert.Convert(key, wantType)
65 if keyErr != nil {
66 return cty.DynamicVal, Diagnostics{
67 {
68 Severity: DiagError,
69 Summary: "Invalid index",
70 Detail: fmt.Sprintf(
71 "The given key does not identify an element in this collection value: %s.",
72 keyErr.Error(),
73 ),
74 Subject: srcRange,
75 },
76 }
77 }
78
79 has := collection.HasIndex(key)
80 if !has.IsKnown() {
81 if ty.IsTupleType() {
82 return cty.DynamicVal, nil
83 } else {
84 return cty.UnknownVal(ty.ElementType()), nil
85 }
86 }
87 if has.False() {
88 // We have a more specialized error message for the situation of
89 // using a fractional number to index into a sequence, because
90 // that will tend to happen if the user is trying to use division
91 // to calculate an index and not realizing that HCL does float
92 // division rather than integer division.
93 if (ty.IsListType() || ty.IsTupleType()) && key.Type().Equals(cty.Number) {
94 if key.IsKnown() && !key.IsNull() {
95 bf := key.AsBigFloat()
96 if _, acc := bf.Int(nil); acc != big.Exact {
97 return cty.DynamicVal, Diagnostics{
98 {
99 Severity: DiagError,
100 Summary: "Invalid index",
101 Detail: fmt.Sprintf("The given key does not identify an element in this collection value: indexing a sequence requires a whole number, but the given index (%g) has a fractional part.", bf),
102 Subject: srcRange,
103 },
104 }
105 }
106 }
107 }
108
109 return cty.DynamicVal, Diagnostics{
110 {
111 Severity: DiagError,
112 Summary: "Invalid index",
113 Detail: "The given key does not identify an element in this collection value.",
114 Subject: srcRange,
115 },
116 }
117 }
118
119 return collection.Index(key), nil
120
121 case ty.IsObjectType():
122 key, keyErr := convert.Convert(key, cty.String)
123 if keyErr != nil {
124 return cty.DynamicVal, Diagnostics{
125 {
126 Severity: DiagError,
127 Summary: "Invalid index",
128 Detail: fmt.Sprintf(
129 "The given key does not identify an element in this collection value: %s.",
130 keyErr.Error(),
131 ),
132 Subject: srcRange,
133 },
134 }
135 }
136 if !collection.IsKnown() {
137 return cty.DynamicVal, nil
138 }
139 if !key.IsKnown() {
140 return cty.DynamicVal, nil
141 }
142
143 attrName := key.AsString()
144
145 if !ty.HasAttribute(attrName) {
146 return cty.DynamicVal, Diagnostics{
147 {
148 Severity: DiagError,
149 Summary: "Invalid index",
150 Detail: "The given key does not identify an element in this collection value.",
151 Subject: srcRange,
152 },
153 }
154 }
155
156 return collection.GetAttr(attrName), nil
157
158 default:
159 return cty.DynamicVal, Diagnostics{
160 {
161 Severity: DiagError,
162 Summary: "Invalid index",
163 Detail: "This value does not have any indices.",
164 Subject: srcRange,
165 },
166 }
167 }
168
169 }
170
171 // GetAttr is a helper function that performs the same operation as the
172 // attribute access in the HCL expression language. That is, the result is the
173 // same as it would be for obj.attr in a configuration expression.
174 //
175 // This is exported so that applications can access attributes in a manner
176 // consistent with how the language does it, including handling of null and
177 // unknown values, etc.
178 //
179 // Diagnostics are produced if the given combination of values is not valid.
180 // Therefore a pointer to a source range must be provided to use in diagnostics,
181 // though nil can be provided if the calling application is going to
182 // ignore the subject of the returned diagnostics anyway.
183 func GetAttr(obj cty.Value, attrName string, srcRange *Range) (cty.Value, Diagnostics) {
184 if obj.IsNull() {
185 return cty.DynamicVal, Diagnostics{
186 {
187 Severity: DiagError,
188 Summary: "Attempt to get attribute from null value",
189 Detail: "This value is null, so it does not have any attributes.",
190 Subject: srcRange,
191 },
192 }
193 }
194
195 ty := obj.Type()
196 switch {
197 case ty.IsObjectType():
198 if !ty.HasAttribute(attrName) {
199 return cty.DynamicVal, Diagnostics{
200 {
201 Severity: DiagError,
202 Summary: "Unsupported attribute",
203 Detail: fmt.Sprintf("This object does not have an attribute named %q.", attrName),
204 Subject: srcRange,
205 },
206 }
207 }
208
209 if !obj.IsKnown() {
210 return cty.UnknownVal(ty.AttributeType(attrName)), nil
211 }
212
213 return obj.GetAttr(attrName), nil
214 case ty.IsMapType():
215 if !obj.IsKnown() {
216 return cty.UnknownVal(ty.ElementType()), nil
217 }
218
219 idx := cty.StringVal(attrName)
220 if obj.HasIndex(idx).False() {
221 return cty.DynamicVal, Diagnostics{
222 {
223 Severity: DiagError,
224 Summary: "Missing map element",
225 Detail: fmt.Sprintf("This map does not have an element with the key %q.", attrName),
226 Subject: srcRange,
227 },
228 }
229 }
230
231 return obj.Index(idx), nil
232 case ty == cty.DynamicPseudoType:
233 return cty.DynamicVal, nil
234 default:
235 return cty.DynamicVal, Diagnostics{
236 {
237 Severity: DiagError,
238 Summary: "Unsupported attribute",
239 Detail: "This value does not have any attributes.",
240 Subject: srcRange,
241 },
242 }
243 }
244
245 }
246
247 // ApplyPath is a helper function that applies a cty.Path to a value using the
248 // indexing and attribute access operations from HCL.
249 //
250 // This is similar to calling the path's own Apply method, but ApplyPath uses
251 // the more relaxed typing rules that apply to these operations in HCL, rather
252 // than cty's relatively-strict rules. ApplyPath is implemented in terms of
253 // Index and GetAttr, and so it has the same behavior for individual steps
254 // but will stop and return any errors returned by intermediate steps.
255 //
256 // Diagnostics are produced if the given path cannot be applied to the given
257 // value. Therefore a pointer to a source range must be provided to use in
258 // diagnostics, though nil can be provided if the calling application is going
259 // to ignore the subject of the returned diagnostics anyway.
260 func ApplyPath(val cty.Value, path cty.Path, srcRange *Range) (cty.Value, Diagnostics) {
261 var diags Diagnostics
262
263 for _, step := range path {
264 var stepDiags Diagnostics
265 switch ts := step.(type) {
266 case cty.IndexStep:
267 val, stepDiags = Index(val, ts.Key, srcRange)
268 case cty.GetAttrStep:
269 val, stepDiags = GetAttr(val, ts.Name, srcRange)
270 default:
271 // Should never happen because the above are all of the step types.
272 diags = diags.Append(&Diagnostic{
273 Severity: DiagError,
274 Summary: "Invalid path step",
275 Detail: fmt.Sprintf("Go type %T is not a valid path step. This is a bug in this program.", step),
276 Subject: srcRange,
277 })
278 return cty.DynamicVal, diags
279 }
280
281 diags = append(diags, stepDiags...)
282 if stepDiags.HasErrors() {
283 return cty.DynamicVal, diags
284 }
285 }
286
287 return val, diags
288 }