import (
"fmt"
+ "math/big"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
}
}
if has.False() {
+ // We have a more specialized error message for the situation of
+ // using a fractional number to index into a sequence, because
+ // that will tend to happen if the user is trying to use division
+ // to calculate an index and not realizing that HCL does float
+ // division rather than integer division.
+ if (ty.IsListType() || ty.IsTupleType()) && key.Type().Equals(cty.Number) {
+ if key.IsKnown() && !key.IsNull() {
+ bf := key.AsBigFloat()
+ if _, acc := bf.Int(nil); acc != big.Exact {
+ return cty.DynamicVal, Diagnostics{
+ {
+ Severity: DiagError,
+ Summary: "Invalid index",
+ 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),
+ Subject: srcRange,
+ },
+ }
+ }
+ }
+ }
+
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
}
}
+
+// GetAttr is a helper function that performs the same operation as the
+// attribute access in the HCL expression language. That is, the result is the
+// same as it would be for obj.attr in a configuration expression.
+//
+// This is exported so that applications can access attributes in a manner
+// consistent with how the language does it, including handling of null and
+// unknown values, etc.
+//
+// Diagnostics are produced if the given combination of values is not valid.
+// Therefore a pointer to a source range must be provided to use in diagnostics,
+// though nil can be provided if the calling application is going to
+// ignore the subject of the returned diagnostics anyway.
+func GetAttr(obj cty.Value, attrName string, srcRange *Range) (cty.Value, Diagnostics) {
+ if obj.IsNull() {
+ return cty.DynamicVal, Diagnostics{
+ {
+ Severity: DiagError,
+ Summary: "Attempt to get attribute from null value",
+ Detail: "This value is null, so it does not have any attributes.",
+ Subject: srcRange,
+ },
+ }
+ }
+
+ ty := obj.Type()
+ switch {
+ case ty.IsObjectType():
+ if !ty.HasAttribute(attrName) {
+ return cty.DynamicVal, Diagnostics{
+ {
+ Severity: DiagError,
+ Summary: "Unsupported attribute",
+ Detail: fmt.Sprintf("This object does not have an attribute named %q.", attrName),
+ Subject: srcRange,
+ },
+ }
+ }
+
+ if !obj.IsKnown() {
+ return cty.UnknownVal(ty.AttributeType(attrName)), nil
+ }
+
+ return obj.GetAttr(attrName), nil
+ case ty.IsMapType():
+ if !obj.IsKnown() {
+ return cty.UnknownVal(ty.ElementType()), nil
+ }
+
+ idx := cty.StringVal(attrName)
+ if obj.HasIndex(idx).False() {
+ return cty.DynamicVal, Diagnostics{
+ {
+ Severity: DiagError,
+ Summary: "Missing map element",
+ Detail: fmt.Sprintf("This map does not have an element with the key %q.", attrName),
+ Subject: srcRange,
+ },
+ }
+ }
+
+ return obj.Index(idx), nil
+ case ty == cty.DynamicPseudoType:
+ return cty.DynamicVal, nil
+ default:
+ return cty.DynamicVal, Diagnostics{
+ {
+ Severity: DiagError,
+ Summary: "Unsupported attribute",
+ Detail: "This value does not have any attributes.",
+ Subject: srcRange,
+ },
+ }
+ }
+
+}
+
+// ApplyPath is a helper function that applies a cty.Path to a value using the
+// indexing and attribute access operations from HCL.
+//
+// This is similar to calling the path's own Apply method, but ApplyPath uses
+// the more relaxed typing rules that apply to these operations in HCL, rather
+// than cty's relatively-strict rules. ApplyPath is implemented in terms of
+// Index and GetAttr, and so it has the same behavior for individual steps
+// but will stop and return any errors returned by intermediate steps.
+//
+// Diagnostics are produced if the given path cannot be applied to the given
+// value. Therefore a pointer to a source range must be provided to use in
+// diagnostics, though nil can be provided if the calling application is going
+// to ignore the subject of the returned diagnostics anyway.
+func ApplyPath(val cty.Value, path cty.Path, srcRange *Range) (cty.Value, Diagnostics) {
+ var diags Diagnostics
+
+ for _, step := range path {
+ var stepDiags Diagnostics
+ switch ts := step.(type) {
+ case cty.IndexStep:
+ val, stepDiags = Index(val, ts.Key, srcRange)
+ case cty.GetAttrStep:
+ val, stepDiags = GetAttr(val, ts.Name, srcRange)
+ default:
+ // Should never happen because the above are all of the step types.
+ diags = diags.Append(&Diagnostic{
+ Severity: DiagError,
+ Summary: "Invalid path step",
+ Detail: fmt.Sprintf("Go type %T is not a valid path step. This is a bug in this program.", step),
+ Subject: srcRange,
+ })
+ return cty.DynamicVal, diags
+ }
+
+ diags = append(diags, stepDiags...)
+ if stepDiags.HasErrors() {
+ return cty.DynamicVal, diags
+ }
+ }
+
+ return val, diags
+}