7 "github.com/zclconf/go-cty/cty"
8 "github.com/zclconf/go-cty/cty/convert"
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.
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.
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{
28 Summary: "Attempt to index null value",
29 Detail: "This value is null, so it does not have any indices.",
35 return cty.DynamicVal, Diagnostics{
38 Summary: "Invalid index",
39 Detail: "Can't use a null value as an indexing key.",
44 ty := collection.Type()
46 if kty == cty.DynamicPseudoType || ty == cty.DynamicPseudoType {
47 return cty.DynamicVal, nil
52 case ty.IsListType() || ty.IsTupleType() || ty.IsMapType():
55 case ty.IsListType() || ty.IsTupleType():
60 // should never happen
61 panic("don't know what key type we want")
64 key, keyErr := convert.Convert(key, wantType)
66 return cty.DynamicVal, Diagnostics{
69 Summary: "Invalid index",
71 "The given key does not identify an element in this collection value: %s.",
79 has := collection.HasIndex(key)
82 return cty.DynamicVal, nil
84 return cty.UnknownVal(ty.ElementType()), nil
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{
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),
109 return cty.DynamicVal, Diagnostics{
112 Summary: "Invalid index",
113 Detail: "The given key does not identify an element in this collection value.",
119 return collection.Index(key), nil
121 case ty.IsObjectType():
122 key, keyErr := convert.Convert(key, cty.String)
124 return cty.DynamicVal, Diagnostics{
127 Summary: "Invalid index",
129 "The given key does not identify an element in this collection value: %s.",
136 if !collection.IsKnown() {
137 return cty.DynamicVal, nil
140 return cty.DynamicVal, nil
143 attrName := key.AsString()
145 if !ty.HasAttribute(attrName) {
146 return cty.DynamicVal, Diagnostics{
149 Summary: "Invalid index",
150 Detail: "The given key does not identify an element in this collection value.",
156 return collection.GetAttr(attrName), nil
159 return cty.DynamicVal, Diagnostics{
162 Summary: "Invalid index",
163 Detail: "This value does not have any indices.",
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.
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.
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) {
185 return cty.DynamicVal, Diagnostics{
188 Summary: "Attempt to get attribute from null value",
189 Detail: "This value is null, so it does not have any attributes.",
197 case ty.IsObjectType():
198 if !ty.HasAttribute(attrName) {
199 return cty.DynamicVal, Diagnostics{
202 Summary: "Unsupported attribute",
203 Detail: fmt.Sprintf("This object does not have an attribute named %q.", attrName),
210 return cty.UnknownVal(ty.AttributeType(attrName)), nil
213 return obj.GetAttr(attrName), nil
216 return cty.UnknownVal(ty.ElementType()), nil
219 idx := cty.StringVal(attrName)
220 if obj.HasIndex(idx).False() {
221 return cty.DynamicVal, Diagnostics{
224 Summary: "Missing map element",
225 Detail: fmt.Sprintf("This map does not have an element with the key %q.", attrName),
231 return obj.Index(idx), nil
232 case ty == cty.DynamicPseudoType:
233 return cty.DynamicVal, nil
235 return cty.DynamicVal, Diagnostics{
238 Summary: "Unsupported attribute",
239 Detail: "This value does not have any attributes.",
247 // ApplyPath is a helper function that applies a cty.Path to a value using the
248 // indexing and attribute access operations from HCL.
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.
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
263 for _, step := range path {
264 var stepDiags Diagnostics
265 switch ts := step.(type) {
267 val, stepDiags = Index(val, ts.Key, srcRange)
268 case cty.GetAttrStep:
269 val, stepDiags = GetAttr(val, ts.Name, srcRange)
271 // Should never happen because the above are all of the step types.
272 diags = diags.Append(&Diagnostic{
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),
278 return cty.DynamicVal, diags
281 diags = append(diags, stepDiags...)
282 if stepDiags.HasErrors() {
283 return cty.DynamicVal, diags