Merge pull request #21174 from hashicorp/jbardin/func-types
lang/funcs: better type handling in interpolation funcs
This commit is contained in:
commit
d2bc9ca406
|
@ -1,6 +1,7 @@
|
||||||
package funcs
|
package funcs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
@ -43,7 +44,7 @@ var ElementFunc = function.New(&function.Spec{
|
||||||
return cty.DynamicPseudoType, fmt.Errorf("invalid index: %s", err)
|
return cty.DynamicPseudoType, fmt.Errorf("invalid index: %s", err)
|
||||||
}
|
}
|
||||||
if len(etys) == 0 {
|
if len(etys) == 0 {
|
||||||
return cty.DynamicPseudoType, fmt.Errorf("cannot use element function with an empty list")
|
return cty.DynamicPseudoType, errors.New("cannot use element function with an empty list")
|
||||||
}
|
}
|
||||||
index = index % len(etys)
|
index = index % len(etys)
|
||||||
return etys[index], nil
|
return etys[index], nil
|
||||||
|
@ -65,7 +66,7 @@ var ElementFunc = function.New(&function.Spec{
|
||||||
|
|
||||||
l := args[0].LengthInt()
|
l := args[0].LengthInt()
|
||||||
if l == 0 {
|
if l == 0 {
|
||||||
return cty.DynamicVal, fmt.Errorf("cannot use element function with an empty list")
|
return cty.DynamicVal, errors.New("cannot use element function with an empty list")
|
||||||
}
|
}
|
||||||
index = index % l
|
index = index % l
|
||||||
|
|
||||||
|
@ -90,7 +91,7 @@ var LengthFunc = function.New(&function.Spec{
|
||||||
case collTy == cty.String || collTy.IsTupleType() || collTy.IsObjectType() || collTy.IsListType() || collTy.IsMapType() || collTy.IsSetType() || collTy == cty.DynamicPseudoType:
|
case collTy == cty.String || collTy.IsTupleType() || collTy.IsObjectType() || collTy.IsListType() || collTy.IsMapType() || collTy.IsSetType() || collTy == cty.DynamicPseudoType:
|
||||||
return cty.Number, nil
|
return cty.Number, nil
|
||||||
default:
|
default:
|
||||||
return cty.Number, fmt.Errorf("argument must be a string, a collection type, or a structural type")
|
return cty.Number, errors.New("argument must be a string, a collection type, or a structural type")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||||
|
@ -114,7 +115,7 @@ var LengthFunc = function.New(&function.Spec{
|
||||||
return coll.Length(), nil
|
return coll.Length(), nil
|
||||||
default:
|
default:
|
||||||
// Should never happen, because of the checks in our Type func above
|
// Should never happen, because of the checks in our Type func above
|
||||||
return cty.UnknownVal(cty.Number), fmt.Errorf("impossible value type for length(...)")
|
return cty.UnknownVal(cty.Number), errors.New("impossible value type for length(...)")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -139,7 +140,7 @@ var CoalesceFunc = function.New(&function.Spec{
|
||||||
}
|
}
|
||||||
retType, _ := convert.UnifyUnsafe(argTypes)
|
retType, _ := convert.UnifyUnsafe(argTypes)
|
||||||
if retType == cty.NilType {
|
if retType == cty.NilType {
|
||||||
return cty.NilType, fmt.Errorf("all arguments must have the same type")
|
return cty.NilType, errors.New("all arguments must have the same type")
|
||||||
}
|
}
|
||||||
return retType, nil
|
return retType, nil
|
||||||
},
|
},
|
||||||
|
@ -159,7 +160,7 @@ var CoalesceFunc = function.New(&function.Spec{
|
||||||
|
|
||||||
return argVal, nil
|
return argVal, nil
|
||||||
}
|
}
|
||||||
return cty.NilVal, fmt.Errorf("no non-null, non-empty-string arguments")
|
return cty.NilVal, errors.New("no non-null, non-empty-string arguments")
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -169,32 +170,43 @@ var CoalesceListFunc = function.New(&function.Spec{
|
||||||
Params: []function.Parameter{},
|
Params: []function.Parameter{},
|
||||||
VarParam: &function.Parameter{
|
VarParam: &function.Parameter{
|
||||||
Name: "vals",
|
Name: "vals",
|
||||||
Type: cty.List(cty.DynamicPseudoType),
|
Type: cty.DynamicPseudoType,
|
||||||
AllowUnknown: true,
|
AllowUnknown: true,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
AllowNull: true,
|
AllowNull: true,
|
||||||
},
|
},
|
||||||
Type: func(args []cty.Value) (ret cty.Type, err error) {
|
Type: func(args []cty.Value) (ret cty.Type, err error) {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return cty.NilType, fmt.Errorf("at least one argument is required")
|
return cty.NilType, errors.New("at least one argument is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
argTypes := make([]cty.Type, len(args))
|
argTypes := make([]cty.Type, len(args))
|
||||||
|
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
|
// if any argument is unknown, we can't be certain know which type we will return
|
||||||
|
if !arg.IsKnown() {
|
||||||
|
return cty.DynamicPseudoType, nil
|
||||||
|
}
|
||||||
|
ty := arg.Type()
|
||||||
|
|
||||||
|
if !ty.IsListType() && !ty.IsTupleType() {
|
||||||
|
return cty.NilType, errors.New("coalescelist arguments must be lists or tuples")
|
||||||
|
}
|
||||||
|
|
||||||
argTypes[i] = arg.Type()
|
argTypes[i] = arg.Type()
|
||||||
}
|
}
|
||||||
|
|
||||||
retType, _ := convert.UnifyUnsafe(argTypes)
|
last := argTypes[0]
|
||||||
if retType == cty.NilType {
|
// If there are mixed types, we have to return a dynamic type.
|
||||||
return cty.NilType, fmt.Errorf("all arguments must have the same type")
|
for _, next := range argTypes[1:] {
|
||||||
|
if !next.Equals(last) {
|
||||||
|
return cty.DynamicPseudoType, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return retType, nil
|
return last, nil
|
||||||
},
|
},
|
||||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
|
|
||||||
vals := make([]cty.Value, 0, len(args))
|
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
if !arg.IsKnown() {
|
if !arg.IsKnown() {
|
||||||
// If we run into an unknown list at some point, we can't
|
// If we run into an unknown list at some point, we can't
|
||||||
|
@ -203,21 +215,12 @@ var CoalesceListFunc = function.New(&function.Spec{
|
||||||
return cty.UnknownVal(retType), nil
|
return cty.UnknownVal(retType), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// We already know this will succeed because of the checks in our Type func above
|
if arg.LengthInt() > 0 {
|
||||||
arg, _ = convert.Convert(arg, retType)
|
return arg, nil
|
||||||
|
|
||||||
it := arg.ElementIterator()
|
|
||||||
for it.Next() {
|
|
||||||
_, v := it.Element()
|
|
||||||
vals = append(vals, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(vals) > 0 {
|
|
||||||
return cty.ListVal(vals), nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cty.NilVal, fmt.Errorf("no non-null arguments")
|
return cty.NilVal, errors.New("no non-null arguments")
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -265,7 +268,7 @@ var ContainsFunc = function.New(&function.Spec{
|
||||||
Params: []function.Parameter{
|
Params: []function.Parameter{
|
||||||
{
|
{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Type: cty.List(cty.DynamicPseudoType),
|
Type: cty.DynamicPseudoType,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "value",
|
Name: "value",
|
||||||
|
@ -274,8 +277,14 @@ var ContainsFunc = function.New(&function.Spec{
|
||||||
},
|
},
|
||||||
Type: function.StaticReturnType(cty.Bool),
|
Type: function.StaticReturnType(cty.Bool),
|
||||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
|
arg := args[0]
|
||||||
|
ty := arg.Type()
|
||||||
|
|
||||||
_, err = Index(args[0], args[1])
|
if !ty.IsListType() && !ty.IsTupleType() && !ty.IsSetType() {
|
||||||
|
return cty.NilVal, errors.New("argument must be list, tuple, or set")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = Index(cty.TupleVal(arg.AsValueSlice()), args[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cty.False, nil
|
return cty.False, nil
|
||||||
}
|
}
|
||||||
|
@ -299,7 +308,7 @@ var IndexFunc = function.New(&function.Spec{
|
||||||
Type: function.StaticReturnType(cty.Number),
|
Type: function.StaticReturnType(cty.Number),
|
||||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
if !(args[0].Type().IsListType() || args[0].Type().IsTupleType()) {
|
if !(args[0].Type().IsListType() || args[0].Type().IsTupleType()) {
|
||||||
return cty.NilVal, fmt.Errorf("argument must be a list or tuple")
|
return cty.NilVal, errors.New("argument must be a list or tuple")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !args[0].IsKnown() {
|
if !args[0].IsKnown() {
|
||||||
|
@ -307,7 +316,7 @@ var IndexFunc = function.New(&function.Spec{
|
||||||
}
|
}
|
||||||
|
|
||||||
if args[0].LengthInt() == 0 { // Easy path
|
if args[0].LengthInt() == 0 { // Easy path
|
||||||
return cty.NilVal, fmt.Errorf("cannot search an empty list")
|
return cty.NilVal, errors.New("cannot search an empty list")
|
||||||
}
|
}
|
||||||
|
|
||||||
for it := args[0].ElementIterator(); it.Next(); {
|
for it := args[0].ElementIterator(); it.Next(); {
|
||||||
|
@ -323,7 +332,7 @@ var IndexFunc = function.New(&function.Spec{
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cty.NilVal, fmt.Errorf("item not found")
|
return cty.NilVal, errors.New("item not found")
|
||||||
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -378,7 +387,7 @@ var ChunklistFunc = function.New(&function.Spec{
|
||||||
},
|
},
|
||||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
listVal := args[0]
|
listVal := args[0]
|
||||||
if !listVal.IsWhollyKnown() {
|
if !listVal.IsKnown() {
|
||||||
return cty.UnknownVal(retType), nil
|
return cty.UnknownVal(retType), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,7 +398,7 @@ var ChunklistFunc = function.New(&function.Spec{
|
||||||
}
|
}
|
||||||
|
|
||||||
if size < 0 {
|
if size < 0 {
|
||||||
return cty.NilVal, fmt.Errorf("the size argument must be positive")
|
return cty.NilVal, errors.New("the size argument must be positive")
|
||||||
}
|
}
|
||||||
|
|
||||||
output := make([]cty.Value, 0)
|
output := make([]cty.Value, 0)
|
||||||
|
@ -437,11 +446,14 @@ var FlattenFunc = function.New(&function.Spec{
|
||||||
|
|
||||||
argTy := args[0].Type()
|
argTy := args[0].Type()
|
||||||
if !argTy.IsListType() && !argTy.IsSetType() && !argTy.IsTupleType() {
|
if !argTy.IsListType() && !argTy.IsSetType() && !argTy.IsTupleType() {
|
||||||
return cty.NilType, fmt.Errorf("can only flatten lists, sets and tuples")
|
return cty.NilType, errors.New("can only flatten lists, sets and tuples")
|
||||||
|
}
|
||||||
|
|
||||||
|
retVal, known := flattener(args[0])
|
||||||
|
if !known {
|
||||||
|
return cty.DynamicPseudoType, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
outputList := make([]cty.Value, 0)
|
|
||||||
retVal := flattener(outputList, args[0])
|
|
||||||
tys := make([]cty.Type, len(retVal))
|
tys := make([]cty.Type, len(retVal))
|
||||||
for i, ty := range retVal {
|
for i, ty := range retVal {
|
||||||
tys[i] = ty.Type()
|
tys[i] = ty.Type()
|
||||||
|
@ -450,30 +462,41 @@ var FlattenFunc = function.New(&function.Spec{
|
||||||
},
|
},
|
||||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
inputList := args[0]
|
inputList := args[0]
|
||||||
if !inputList.IsWhollyKnown() {
|
|
||||||
return cty.UnknownVal(retType), nil
|
|
||||||
}
|
|
||||||
if inputList.LengthInt() == 0 {
|
if inputList.LengthInt() == 0 {
|
||||||
return cty.EmptyTupleVal, nil
|
return cty.EmptyTupleVal, nil
|
||||||
}
|
}
|
||||||
outputList := make([]cty.Value, 0)
|
|
||||||
|
|
||||||
return cty.TupleVal(flattener(outputList, inputList)), nil
|
out, known := flattener(inputList)
|
||||||
|
if !known {
|
||||||
|
return cty.UnknownVal(retType), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return cty.TupleVal(out), nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Flatten until it's not a cty.List
|
// Flatten until it's not a cty.List, and return whether the value is known.
|
||||||
func flattener(finalList []cty.Value, flattenList cty.Value) []cty.Value {
|
// We can flatten lists with unknown values, as long as they are not
|
||||||
|
// lists themselves.
|
||||||
|
func flattener(flattenList cty.Value) ([]cty.Value, bool) {
|
||||||
|
out := make([]cty.Value, 0)
|
||||||
for it := flattenList.ElementIterator(); it.Next(); {
|
for it := flattenList.ElementIterator(); it.Next(); {
|
||||||
_, val := it.Element()
|
_, val := it.Element()
|
||||||
|
|
||||||
if val.Type().IsListType() || val.Type().IsSetType() || val.Type().IsTupleType() {
|
if val.Type().IsListType() || val.Type().IsSetType() || val.Type().IsTupleType() {
|
||||||
finalList = flattener(finalList, val)
|
if !val.IsKnown() {
|
||||||
|
return out, false
|
||||||
|
}
|
||||||
|
|
||||||
|
res, known := flattener(val)
|
||||||
|
if !known {
|
||||||
|
return res, known
|
||||||
|
}
|
||||||
|
out = append(out, res...)
|
||||||
} else {
|
} else {
|
||||||
finalList = append(finalList, val)
|
out = append(out, val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return finalList
|
return out, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeysFunc contructs a function that takes a map and returns a sorted list of the map keys.
|
// KeysFunc contructs a function that takes a map and returns a sorted list of the map keys.
|
||||||
|
@ -561,7 +584,7 @@ var ListFunc = function.New(&function.Spec{
|
||||||
},
|
},
|
||||||
Type: func(args []cty.Value) (ret cty.Type, err error) {
|
Type: func(args []cty.Value) (ret cty.Type, err error) {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return cty.NilType, fmt.Errorf("at least one argument is required")
|
return cty.NilType, errors.New("at least one argument is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
argTypes := make([]cty.Type, len(args))
|
argTypes := make([]cty.Type, len(args))
|
||||||
|
@ -572,7 +595,7 @@ var ListFunc = function.New(&function.Spec{
|
||||||
|
|
||||||
retType, _ := convert.UnifyUnsafe(argTypes)
|
retType, _ := convert.UnifyUnsafe(argTypes)
|
||||||
if retType == cty.NilType {
|
if retType == cty.NilType {
|
||||||
return cty.NilType, fmt.Errorf("all arguments must have the same type")
|
return cty.NilType, errors.New("all arguments must have the same type")
|
||||||
}
|
}
|
||||||
|
|
||||||
return cty.List(retType), nil
|
return cty.List(retType), nil
|
||||||
|
@ -666,7 +689,7 @@ var LookupFunc = function.New(&function.Spec{
|
||||||
case ty.Equals(cty.Number):
|
case ty.Equals(cty.Number):
|
||||||
return cty.NumberVal(v.AsBigFloat()), nil
|
return cty.NumberVal(v.AsBigFloat()), nil
|
||||||
default:
|
default:
|
||||||
return cty.NilVal, fmt.Errorf("lookup() can only be used with flat lists")
|
return cty.NilVal, errors.New("lookup() can only be used with flat lists")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -712,7 +735,7 @@ var MapFunc = function.New(&function.Spec{
|
||||||
|
|
||||||
valType, _ := convert.UnifyUnsafe(argTypes)
|
valType, _ := convert.UnifyUnsafe(argTypes)
|
||||||
if valType == cty.NilType {
|
if valType == cty.NilType {
|
||||||
return cty.NilType, fmt.Errorf("all arguments must have the same type")
|
return cty.NilType, errors.New("all arguments must have the same type")
|
||||||
}
|
}
|
||||||
|
|
||||||
return cty.Map(valType), nil
|
return cty.Map(valType), nil
|
||||||
|
@ -777,7 +800,7 @@ var MatchkeysFunc = function.New(&function.Spec{
|
||||||
},
|
},
|
||||||
Type: func(args []cty.Value) (cty.Type, error) {
|
Type: func(args []cty.Value) (cty.Type, error) {
|
||||||
if !args[1].Type().Equals(args[2].Type()) {
|
if !args[1].Type().Equals(args[2].Type()) {
|
||||||
return cty.NilType, fmt.Errorf("lists must be of the same type")
|
return cty.NilType, errors.New("lists must be of the same type")
|
||||||
}
|
}
|
||||||
|
|
||||||
return args[0].Type(), nil
|
return args[0].Type(), nil
|
||||||
|
@ -788,7 +811,7 @@ var MatchkeysFunc = function.New(&function.Spec{
|
||||||
}
|
}
|
||||||
|
|
||||||
if args[0].LengthInt() != args[1].LengthInt() {
|
if args[0].LengthInt() != args[1].LengthInt() {
|
||||||
return cty.ListValEmpty(retType.ElementType()), fmt.Errorf("length of keys and values should be equal")
|
return cty.ListValEmpty(retType.ElementType()), errors.New("length of keys and values should be equal")
|
||||||
}
|
}
|
||||||
|
|
||||||
output := make([]cty.Value, 0)
|
output := make([]cty.Value, 0)
|
||||||
|
@ -923,7 +946,7 @@ var SetProductFunc = function.New(&function.Spec{
|
||||||
},
|
},
|
||||||
Type: func(args []cty.Value) (retType cty.Type, err error) {
|
Type: func(args []cty.Value) (retType cty.Type, err error) {
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return cty.NilType, fmt.Errorf("at least two arguments are required")
|
return cty.NilType, errors.New("at least two arguments are required")
|
||||||
}
|
}
|
||||||
|
|
||||||
listCount := 0
|
listCount := 0
|
||||||
|
@ -1040,7 +1063,7 @@ var SliceFunc = function.New(&function.Spec{
|
||||||
Params: []function.Parameter{
|
Params: []function.Parameter{
|
||||||
{
|
{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Type: cty.List(cty.DynamicPseudoType),
|
Type: cty.DynamicPseudoType,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "startIndex",
|
Name: "startIndex",
|
||||||
|
@ -1052,50 +1075,71 @@ var SliceFunc = function.New(&function.Spec{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Type: func(args []cty.Value) (cty.Type, error) {
|
Type: func(args []cty.Value) (cty.Type, error) {
|
||||||
return args[0].Type(), nil
|
arg := args[0]
|
||||||
|
argTy := arg.Type()
|
||||||
|
|
||||||
|
if !argTy.IsListType() && !argTy.IsTupleType() {
|
||||||
|
return cty.NilType, errors.New("cannot slice a set, because its elements do not have indices; use the tolist function to force conversion to list if the ordering of the result is not important")
|
||||||
|
}
|
||||||
|
|
||||||
|
if argTy.IsListType() {
|
||||||
|
return argTy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
startIndex, endIndex, err := sliceIndexes(args, args[0].LengthInt())
|
||||||
|
if err != nil {
|
||||||
|
return cty.NilType, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cty.Tuple(argTy.TupleElementTypes()[startIndex:endIndex]), nil
|
||||||
},
|
},
|
||||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
inputList := args[0]
|
inputList := args[0]
|
||||||
if !inputList.IsWhollyKnown() {
|
|
||||||
return cty.UnknownVal(retType), nil
|
|
||||||
}
|
|
||||||
var startIndex, endIndex int
|
|
||||||
|
|
||||||
if err = gocty.FromCtyValue(args[1], &startIndex); err != nil {
|
startIndex, endIndex, err := sliceIndexes(args, inputList.LengthInt())
|
||||||
return cty.NilVal, fmt.Errorf("invalid start index: %s", err)
|
if err != nil {
|
||||||
}
|
return cty.NilVal, err
|
||||||
if err = gocty.FromCtyValue(args[2], &endIndex); err != nil {
|
|
||||||
return cty.NilVal, fmt.Errorf("invalid start index: %s", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if startIndex < 0 {
|
if endIndex-startIndex == 0 {
|
||||||
return cty.NilVal, fmt.Errorf("from index must be >= 0")
|
if retType.IsTupleType() {
|
||||||
|
return cty.EmptyTupleVal, nil
|
||||||
}
|
}
|
||||||
if endIndex > inputList.LengthInt() {
|
|
||||||
return cty.NilVal, fmt.Errorf("to index must be <= length of the input list")
|
|
||||||
}
|
|
||||||
if startIndex > endIndex {
|
|
||||||
return cty.NilVal, fmt.Errorf("from index must be <= to index")
|
|
||||||
}
|
|
||||||
|
|
||||||
var outputList []cty.Value
|
|
||||||
|
|
||||||
i := 0
|
|
||||||
for it := inputList.ElementIterator(); it.Next(); {
|
|
||||||
_, v := it.Element()
|
|
||||||
if i >= startIndex && i < endIndex {
|
|
||||||
outputList = append(outputList, v)
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(outputList) == 0 {
|
|
||||||
return cty.ListValEmpty(retType.ElementType()), nil
|
return cty.ListValEmpty(retType.ElementType()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outputList := inputList.AsValueSlice()[startIndex:endIndex]
|
||||||
|
|
||||||
|
if retType.IsTupleType() {
|
||||||
|
return cty.TupleVal(outputList), nil
|
||||||
|
}
|
||||||
|
|
||||||
return cty.ListVal(outputList), nil
|
return cty.ListVal(outputList), nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
func sliceIndexes(args []cty.Value, max int) (int, int, error) {
|
||||||
|
var startIndex, endIndex int
|
||||||
|
|
||||||
|
if err := gocty.FromCtyValue(args[1], &startIndex); err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("invalid start index: %s", err)
|
||||||
|
}
|
||||||
|
if err := gocty.FromCtyValue(args[2], &endIndex); err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("invalid start index: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if startIndex < 0 {
|
||||||
|
return 0, 0, errors.New("from index must be greater than or equal to 0")
|
||||||
|
}
|
||||||
|
if endIndex > max {
|
||||||
|
return 0, 0, errors.New("to index must be less than or equal to the length of the input list")
|
||||||
|
}
|
||||||
|
if startIndex > endIndex {
|
||||||
|
return 0, 0, errors.New("from index must be less than or equal to index")
|
||||||
|
}
|
||||||
|
return startIndex, endIndex, nil
|
||||||
|
}
|
||||||
|
|
||||||
// TransposeFunc contructs a function that takes a map of lists of strings and
|
// TransposeFunc contructs a function that takes a map of lists of strings and
|
||||||
// swaps the keys and values to produce a new map of lists of strings.
|
// swaps the keys and values to produce a new map of lists of strings.
|
||||||
var TransposeFunc = function.New(&function.Spec{
|
var TransposeFunc = function.New(&function.Spec{
|
||||||
|
@ -1120,7 +1164,7 @@ var TransposeFunc = function.New(&function.Spec{
|
||||||
for iter := inVal.ElementIterator(); iter.Next(); {
|
for iter := inVal.ElementIterator(); iter.Next(); {
|
||||||
_, val := iter.Element()
|
_, val := iter.Element()
|
||||||
if !val.Type().Equals(cty.String) {
|
if !val.Type().Equals(cty.String) {
|
||||||
return cty.MapValEmpty(cty.List(cty.String)), fmt.Errorf("input must be a map of lists of strings")
|
return cty.MapValEmpty(cty.List(cty.String)), errors.New("input must be a map of lists of strings")
|
||||||
}
|
}
|
||||||
|
|
||||||
outKey := val.AsString()
|
outKey := val.AsString()
|
||||||
|
@ -1180,7 +1224,7 @@ var ValuesFunc = function.New(&function.Spec{
|
||||||
}
|
}
|
||||||
return cty.Tuple(tys), nil
|
return cty.Tuple(tys), nil
|
||||||
}
|
}
|
||||||
return cty.NilType, fmt.Errorf("values() requires a map as the first argument")
|
return cty.NilType, errors.New("values() requires a map as the first argument")
|
||||||
},
|
},
|
||||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
mapVar := args[0]
|
mapVar := args[0]
|
||||||
|
@ -1247,7 +1291,7 @@ var ZipmapFunc = function.New(&function.Spec{
|
||||||
return cty.Object(atys), nil
|
return cty.Object(atys), nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return cty.NilType, fmt.Errorf("values argument must be a list or tuple value")
|
return cty.NilType, errors.New("values argument must be a list or tuple value")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
|
|
|
@ -417,7 +417,7 @@ func TestCoalesceList(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
cty.ListVal([]cty.Value{
|
cty.ListVal([]cty.Value{
|
||||||
cty.StringVal("1"), cty.StringVal("2"),
|
cty.NumberIntVal(1), cty.NumberIntVal(2),
|
||||||
}),
|
}),
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
@ -476,7 +476,7 @@ func TestCoalesceList(t *testing.T) {
|
||||||
cty.ListValEmpty(cty.String),
|
cty.ListValEmpty(cty.String),
|
||||||
cty.UnknownVal(cty.List(cty.String)),
|
cty.UnknownVal(cty.List(cty.String)),
|
||||||
},
|
},
|
||||||
cty.UnknownVal(cty.List(cty.String)),
|
cty.DynamicVal,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{ // unknown list
|
{ // unknown list
|
||||||
|
@ -486,13 +486,63 @@ func TestCoalesceList(t *testing.T) {
|
||||||
cty.StringVal("third"), cty.StringVal("fourth"),
|
cty.StringVal("third"), cty.StringVal("fourth"),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
cty.UnknownVal(cty.List(cty.String)),
|
cty.DynamicVal,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
{ // unknown tuple
|
||||||
|
[]cty.Value{
|
||||||
|
cty.UnknownVal(cty.Tuple([]cty.Type{cty.String})),
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("third"), cty.StringVal("fourth"),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
cty.DynamicVal,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // empty tuple
|
||||||
|
[]cty.Value{
|
||||||
|
cty.EmptyTupleVal,
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("third"), cty.StringVal("fourth"),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("third"), cty.StringVal("fourth"),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // tuple value
|
||||||
|
[]cty.Value{
|
||||||
|
cty.TupleVal([]cty.Value{
|
||||||
|
cty.StringVal("a"),
|
||||||
|
cty.NumberIntVal(2),
|
||||||
|
}),
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("third"), cty.StringVal("fourth"),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
cty.TupleVal([]cty.Value{
|
||||||
|
cty.StringVal("a"),
|
||||||
|
cty.NumberIntVal(2),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // reject set value
|
||||||
|
[]cty.Value{
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.StringVal("a"),
|
||||||
|
}),
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("third"), cty.StringVal("fourth"),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
cty.NilVal,
|
||||||
|
true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for i, test := range tests {
|
||||||
t.Run(fmt.Sprintf("coalescelist(%#v)", test.Values), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%d-coalescelist(%#v)", i, test.Values), func(t *testing.T) {
|
||||||
got, err := CoalesceList(test.Values...)
|
got, err := CoalesceList(test.Values...)
|
||||||
|
|
||||||
if test.Err {
|
if test.Err {
|
||||||
|
@ -671,6 +721,26 @@ func TestContains(t *testing.T) {
|
||||||
cty.BoolVal(true),
|
cty.BoolVal(true),
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
{ // set val
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.StringVal("quick"),
|
||||||
|
cty.StringVal("brown"),
|
||||||
|
cty.StringVal("fox"),
|
||||||
|
}),
|
||||||
|
cty.StringVal("quick"),
|
||||||
|
cty.BoolVal(true),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // tuple val
|
||||||
|
cty.TupleVal([]cty.Value{
|
||||||
|
cty.StringVal("quick"),
|
||||||
|
cty.StringVal("brown"),
|
||||||
|
cty.NumberIntVal(3),
|
||||||
|
}),
|
||||||
|
cty.NumberIntVal(3),
|
||||||
|
cty.BoolVal(true),
|
||||||
|
false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
@ -993,13 +1063,29 @@ func TestChunklist(t *testing.T) {
|
||||||
cty.UnknownVal(cty.String),
|
cty.UnknownVal(cty.String),
|
||||||
}),
|
}),
|
||||||
cty.NumberIntVal(1),
|
cty.NumberIntVal(1),
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("a"),
|
||||||
|
}),
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("b"),
|
||||||
|
}),
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.UnknownVal(cty.String),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.UnknownVal(cty.List(cty.String)),
|
||||||
|
cty.NumberIntVal(1),
|
||||||
cty.UnknownVal(cty.List(cty.List(cty.String))),
|
cty.UnknownVal(cty.List(cty.List(cty.String))),
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for i, test := range tests {
|
||||||
t.Run(fmt.Sprintf("chunklist(%#v, %#v)", test.List, test.Size), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%d-chunklist(%#v, %#v)", i, test.List, test.Size), func(t *testing.T) {
|
||||||
got, err := Chunklist(test.List, test.Size)
|
got, err := Chunklist(test.List, test.Size)
|
||||||
|
|
||||||
if test.Err {
|
if test.Err {
|
||||||
|
@ -1043,6 +1129,44 @@ func TestFlatten(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
// handle single elements as arguments
|
||||||
|
{
|
||||||
|
cty.TupleVal([]cty.Value{
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("a"),
|
||||||
|
cty.StringVal("b"),
|
||||||
|
}),
|
||||||
|
cty.StringVal("c"),
|
||||||
|
}),
|
||||||
|
cty.TupleVal([]cty.Value{
|
||||||
|
cty.StringVal("a"),
|
||||||
|
cty.StringVal("b"),
|
||||||
|
cty.StringVal("c"),
|
||||||
|
}), false,
|
||||||
|
},
|
||||||
|
// handle single elements and mixed primitive types as arguments
|
||||||
|
{
|
||||||
|
cty.TupleVal([]cty.Value{
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("a"),
|
||||||
|
cty.StringVal("b"),
|
||||||
|
}),
|
||||||
|
cty.StringVal("c"),
|
||||||
|
cty.TupleVal([]cty.Value{
|
||||||
|
cty.StringVal("x"),
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.TupleVal([]cty.Value{
|
||||||
|
cty.StringVal("a"),
|
||||||
|
cty.StringVal("b"),
|
||||||
|
cty.StringVal("c"),
|
||||||
|
cty.StringVal("x"),
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
// Primitive unknowns should still be flattened to a tuple
|
||||||
{
|
{
|
||||||
cty.ListVal([]cty.Value{
|
cty.ListVal([]cty.Value{
|
||||||
cty.ListVal([]cty.Value{
|
cty.ListVal([]cty.Value{
|
||||||
|
@ -1054,8 +1178,26 @@ func TestFlatten(t *testing.T) {
|
||||||
cty.StringVal("d"),
|
cty.StringVal("d"),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
cty.DynamicVal,
|
cty.TupleVal([]cty.Value{
|
||||||
false,
|
cty.StringVal("a"),
|
||||||
|
cty.StringVal("b"),
|
||||||
|
cty.UnknownVal(cty.String),
|
||||||
|
cty.StringVal("d"),
|
||||||
|
}), false,
|
||||||
|
},
|
||||||
|
// An unknown series should return an unknown dynamic value
|
||||||
|
{
|
||||||
|
cty.TupleVal([]cty.Value{
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("a"),
|
||||||
|
cty.StringVal("b"),
|
||||||
|
}),
|
||||||
|
cty.TupleVal([]cty.Value{
|
||||||
|
cty.UnknownVal(cty.List(cty.String)),
|
||||||
|
cty.StringVal("d"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.UnknownVal(cty.DynamicPseudoType), false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cty.ListValEmpty(cty.String),
|
cty.ListValEmpty(cty.String),
|
||||||
|
@ -1102,8 +1244,8 @@ func TestFlatten(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for i, test := range tests {
|
||||||
t.Run(fmt.Sprintf("flatten(%#v)", test.List), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%d-flatten(%#v)", i, test.List), func(t *testing.T) {
|
||||||
got, err := Flatten(test.List)
|
got, err := Flatten(test.List)
|
||||||
|
|
||||||
if test.Err {
|
if test.Err {
|
||||||
|
@ -2354,6 +2496,11 @@ func TestSlice(t *testing.T) {
|
||||||
cty.StringVal("a"),
|
cty.StringVal("a"),
|
||||||
cty.UnknownVal(cty.String),
|
cty.UnknownVal(cty.String),
|
||||||
})
|
})
|
||||||
|
tuple := cty.TupleVal([]cty.Value{
|
||||||
|
cty.StringVal("a"),
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
cty.UnknownVal(cty.List(cty.String)),
|
||||||
|
})
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
List cty.Value
|
List cty.Value
|
||||||
StartIndex cty.Value
|
StartIndex cty.Value
|
||||||
|
@ -2370,10 +2517,24 @@ func TestSlice(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{ // unknowns in the list
|
{ // slice only an unknown value
|
||||||
listWithUnknowns,
|
listWithUnknowns,
|
||||||
cty.NumberIntVal(1),
|
cty.NumberIntVal(1),
|
||||||
cty.NumberIntVal(2),
|
cty.NumberIntVal(2),
|
||||||
|
cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // slice multiple values, which contain an unknown
|
||||||
|
listWithUnknowns,
|
||||||
|
cty.NumberIntVal(0),
|
||||||
|
cty.NumberIntVal(2),
|
||||||
|
listWithUnknowns,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // an unknown list should be slicable, returning an unknown list
|
||||||
|
cty.UnknownVal(cty.List(cty.String)),
|
||||||
|
cty.NumberIntVal(0),
|
||||||
|
cty.NumberIntVal(2),
|
||||||
cty.UnknownVal(cty.List(cty.String)),
|
cty.UnknownVal(cty.List(cty.String)),
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
@ -2414,10 +2575,44 @@ func TestSlice(t *testing.T) {
|
||||||
cty.NilVal,
|
cty.NilVal,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
{ // sets are not slice-able
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.StringVal("x"),
|
||||||
|
cty.StringVal("y"),
|
||||||
|
}),
|
||||||
|
cty.NumberIntVal(0),
|
||||||
|
cty.NumberIntVal(0),
|
||||||
|
cty.NilVal,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{ // tuple slice
|
||||||
|
tuple,
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
cty.NumberIntVal(3),
|
||||||
|
cty.TupleVal([]cty.Value{
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
cty.UnknownVal(cty.List(cty.String)),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // empty list slice
|
||||||
|
listOfStrings,
|
||||||
|
cty.NumberIntVal(2),
|
||||||
|
cty.NumberIntVal(2),
|
||||||
|
cty.ListValEmpty(cty.String),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // empty tuple slice
|
||||||
|
tuple,
|
||||||
|
cty.NumberIntVal(3),
|
||||||
|
cty.NumberIntVal(3),
|
||||||
|
cty.EmptyTupleVal,
|
||||||
|
false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for i, test := range tests {
|
||||||
t.Run(fmt.Sprintf("slice(%#v, %#v, %#v)", test.List, test.StartIndex, test.EndIndex), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%d-slice(%#v, %#v, %#v)", i, test.List, test.StartIndex, test.EndIndex), func(t *testing.T) {
|
||||||
got, err := Slice(test.List, test.StartIndex, test.EndIndex)
|
got, err := Slice(test.List, test.StartIndex, test.EndIndex)
|
||||||
|
|
||||||
if test.Err {
|
if test.Err {
|
||||||
|
|
|
@ -155,7 +155,7 @@ func TestFunctions(t *testing.T) {
|
||||||
|
|
||||||
{
|
{
|
||||||
`coalescelist(["first", "second"], ["third", "fourth"])`,
|
`coalescelist(["first", "second"], ["third", "fourth"])`,
|
||||||
cty.ListVal([]cty.Value{
|
cty.TupleVal([]cty.Value{
|
||||||
cty.StringVal("first"), cty.StringVal("second"),
|
cty.StringVal("first"), cty.StringVal("second"),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -163,12 +163,19 @@ func TestFunctions(t *testing.T) {
|
||||||
|
|
||||||
"coalescelist": {
|
"coalescelist": {
|
||||||
{
|
{
|
||||||
`coalescelist(["a", "b"], ["c", "d"])`,
|
`coalescelist(list("a", "b"), list("c", "d"))`,
|
||||||
cty.ListVal([]cty.Value{
|
cty.ListVal([]cty.Value{
|
||||||
cty.StringVal("a"),
|
cty.StringVal("a"),
|
||||||
cty.StringVal("b"),
|
cty.StringVal("b"),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
`coalescelist(["a", "b"], ["c", "d"])`,
|
||||||
|
cty.TupleVal([]cty.Value{
|
||||||
|
cty.StringVal("a"),
|
||||||
|
cty.StringVal("b"),
|
||||||
|
}),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"compact": {
|
"compact": {
|
||||||
|
@ -592,11 +599,18 @@ func TestFunctions(t *testing.T) {
|
||||||
|
|
||||||
"slice": {
|
"slice": {
|
||||||
{
|
{
|
||||||
`slice(["a", "b", "c", "d"], 1, 3)`,
|
// force a list type here for testing
|
||||||
|
`slice(list("a", "b", "c", "d"), 1, 3)`,
|
||||||
cty.ListVal([]cty.Value{
|
cty.ListVal([]cty.Value{
|
||||||
cty.StringVal("b"), cty.StringVal("c"),
|
cty.StringVal("b"), cty.StringVal("c"),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
`slice(["a", "b", 3, 4], 1, 3)`,
|
||||||
|
cty.TupleVal([]cty.Value{
|
||||||
|
cty.StringVal("b"), cty.NumberIntVal(3),
|
||||||
|
}),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"sort": {
|
"sort": {
|
||||||
|
|
Loading…
Reference in New Issue