diff --git a/lang/funcs/collection.go b/lang/funcs/collection.go index 21ba0abb2..a6eb16fba 100644 --- a/lang/funcs/collection.go +++ b/lang/funcs/collection.go @@ -12,70 +12,6 @@ import ( "github.com/zclconf/go-cty/cty/gocty" ) -var ElementFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "list", - Type: cty.DynamicPseudoType, - }, - { - Name: "index", - Type: cty.Number, - }, - }, - Type: func(args []cty.Value) (cty.Type, error) { - list := args[0] - listTy := list.Type() - switch { - case listTy.IsListType(): - return listTy.ElementType(), nil - case listTy.IsTupleType(): - if !args[1].IsKnown() { - // If the index isn't known yet then we can't predict the - // result type since each tuple element can have its own type. - return cty.DynamicPseudoType, nil - } - - etys := listTy.TupleElementTypes() - var index int - err := gocty.FromCtyValue(args[1], &index) - if err != nil { - // e.g. fractional number where whole number is required - return cty.DynamicPseudoType, fmt.Errorf("invalid index: %s", err) - } - if len(etys) == 0 { - return cty.DynamicPseudoType, errors.New("cannot use element function with an empty list") - } - index = index % len(etys) - return etys[index], nil - default: - return cty.DynamicPseudoType, fmt.Errorf("cannot read elements from %s", listTy.FriendlyName()) - } - }, - Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { - var index int - err := gocty.FromCtyValue(args[1], &index) - if err != nil { - // can't happen because we checked this in the Type function above - return cty.DynamicVal, fmt.Errorf("invalid index: %s", err) - } - - if !args[0].IsKnown() { - return cty.UnknownVal(retType), nil - } - - l := args[0].LengthInt() - if l == 0 { - return cty.DynamicVal, errors.New("cannot use element function with an empty list") - } - index = index % l - - // We did all the necessary type checks in the type function above, - // so this is guaranteed not to fail. - return args[0].Index(cty.NumberIntVal(int64(index))), nil - }, -}) - var LengthFunc = function.New(&function.Spec{ Params: []function.Parameter{ { @@ -164,133 +100,6 @@ var CoalesceFunc = function.New(&function.Spec{ }, }) -// CoalesceListFunc constructs a function that takes any number of list arguments -// and returns the first one that isn't empty. -var CoalesceListFunc = function.New(&function.Spec{ - Params: []function.Parameter{}, - VarParam: &function.Parameter{ - Name: "vals", - Type: cty.DynamicPseudoType, - AllowUnknown: true, - AllowDynamicType: true, - AllowNull: true, - }, - Type: func(args []cty.Value) (ret cty.Type, err error) { - if len(args) == 0 { - return cty.NilType, errors.New("at least one argument is required") - } - - argTypes := make([]cty.Type, len(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() - } - - last := argTypes[0] - // If there are mixed types, we have to return a dynamic type. - for _, next := range argTypes[1:] { - if !next.Equals(last) { - return cty.DynamicPseudoType, nil - } - } - - return last, nil - }, - Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { - for _, arg := range args { - if !arg.IsKnown() { - // If we run into an unknown list at some point, we can't - // predict the final result yet. (If there's a known, non-empty - // arg before this then we won't get here.) - return cty.UnknownVal(retType), nil - } - - if arg.LengthInt() > 0 { - return arg, nil - } - } - - return cty.NilVal, errors.New("no non-null arguments") - }, -}) - -// CompactFunc constructs a function that takes a list of strings and returns a new list -// with any empty string elements removed. -var CompactFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "list", - Type: cty.List(cty.String), - }, - }, - Type: function.StaticReturnType(cty.List(cty.String)), - Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { - listVal := args[0] - if !listVal.IsWhollyKnown() { - // If some of the element values aren't known yet then we - // can't yet return a compacted list - return cty.UnknownVal(retType), nil - } - - var outputList []cty.Value - - for it := listVal.ElementIterator(); it.Next(); { - _, v := it.Element() - if v.IsNull() || v.AsString() == "" { - continue - } - outputList = append(outputList, v) - } - - if len(outputList) == 0 { - return cty.ListValEmpty(cty.String), nil - } - - return cty.ListVal(outputList), nil - }, -}) - -// ContainsFunc constructs a function that determines whether a given list or -// set contains a given single value as one of its elements. -var ContainsFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "list", - Type: cty.DynamicPseudoType, - }, - { - Name: "value", - Type: cty.DynamicPseudoType, - }, - }, - Type: function.StaticReturnType(cty.Bool), - Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { - arg := args[0] - ty := arg.Type() - - 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 { - return cty.False, nil - } - - return cty.True, nil - }, -}) - // IndexFunc constructs a function that finds the element index for a given value in a list. var IndexFunc = function.New(&function.Spec{ Params: []function.Parameter{ @@ -335,151 +144,6 @@ var IndexFunc = function.New(&function.Spec{ }, }) -// DistinctFunc constructs a function that takes a list and returns a new list -// with any duplicate elements removed. -var DistinctFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "list", - Type: cty.List(cty.DynamicPseudoType), - }, - }, - Type: func(args []cty.Value) (cty.Type, error) { - return args[0].Type(), nil - }, - Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { - listVal := args[0] - - if !listVal.IsWhollyKnown() { - return cty.UnknownVal(retType), nil - } - var list []cty.Value - - for it := listVal.ElementIterator(); it.Next(); { - _, v := it.Element() - list, err = appendIfMissing(list, v) - if err != nil { - return cty.NilVal, err - } - } - - if len(list) == 0 { - return cty.ListValEmpty(retType.ElementType()), nil - } - return cty.ListVal(list), nil - }, -}) - -// ChunklistFunc constructs a function that splits a single list into fixed-size chunks, -// returning a list of lists. -var ChunklistFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "list", - Type: cty.List(cty.DynamicPseudoType), - }, - { - Name: "size", - Type: cty.Number, - }, - }, - Type: func(args []cty.Value) (cty.Type, error) { - return cty.List(args[0].Type()), nil - }, - Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { - listVal := args[0] - if !listVal.IsKnown() { - return cty.UnknownVal(retType), nil - } - - if listVal.LengthInt() == 0 { - return cty.ListValEmpty(listVal.Type()), nil - } - - var size int - err = gocty.FromCtyValue(args[1], &size) - if err != nil { - return cty.NilVal, fmt.Errorf("invalid index: %s", err) - } - - if size < 0 { - return cty.NilVal, errors.New("the size argument must be positive") - } - - output := make([]cty.Value, 0) - - // if size is 0, returns a list made of the initial list - if size == 0 { - output = append(output, listVal) - return cty.ListVal(output), nil - } - - chunk := make([]cty.Value, 0) - - l := args[0].LengthInt() - i := 0 - - for it := listVal.ElementIterator(); it.Next(); { - _, v := it.Element() - chunk = append(chunk, v) - - // Chunk when index isn't 0, or when reaching the values's length - if (i+1)%size == 0 || (i+1) == l { - output = append(output, cty.ListVal(chunk)) - chunk = make([]cty.Value, 0) - } - i++ - } - - return cty.ListVal(output), nil - }, -}) - -// FlattenFunc constructs a function that takes a list and replaces any elements -// that are lists with a flattened sequence of the list contents. -var FlattenFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "list", - Type: cty.DynamicPseudoType, - }, - }, - Type: func(args []cty.Value) (cty.Type, error) { - if !args[0].IsWhollyKnown() { - return cty.DynamicPseudoType, nil - } - - argTy := args[0].Type() - if !argTy.IsListType() && !argTy.IsSetType() && !argTy.IsTupleType() { - return cty.NilType, errors.New("can only flatten lists, sets and tuples") - } - - retVal, known := flattener(args[0]) - if !known { - return cty.DynamicPseudoType, nil - } - - tys := make([]cty.Type, len(retVal)) - for i, ty := range retVal { - tys[i] = ty.Type() - } - return cty.Tuple(tys), nil - }, - Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { - inputList := args[0] - if inputList.LengthInt() == 0 { - return cty.EmptyTupleVal, 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, and return whether the value is known. // We can flatten lists with unknown values, as long as they are not // lists themselves. @@ -504,76 +168,6 @@ func flattener(flattenList cty.Value) ([]cty.Value, bool) { return out, true } -// KeysFunc constructs a function that takes a map and returns a sorted list of the map keys. -var KeysFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "inputMap", - Type: cty.DynamicPseudoType, - AllowUnknown: true, - }, - }, - Type: func(args []cty.Value) (cty.Type, error) { - ty := args[0].Type() - switch { - case ty.IsMapType(): - return cty.List(cty.String), nil - case ty.IsObjectType(): - atys := ty.AttributeTypes() - if len(atys) == 0 { - return cty.EmptyTuple, nil - } - // All of our result elements will be strings, and atys just - // decides how many there are. - etys := make([]cty.Type, len(atys)) - for i := range etys { - etys[i] = cty.String - } - return cty.Tuple(etys), nil - default: - return cty.DynamicPseudoType, function.NewArgErrorf(0, "must have map or object type") - } - }, - Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { - m := args[0] - var keys []cty.Value - - switch { - case m.Type().IsObjectType(): - // In this case we allow unknown values so we must work only with - // the attribute _types_, not with the value itself. - var names []string - for name := range m.Type().AttributeTypes() { - names = append(names, name) - } - sort.Strings(names) // same ordering guaranteed by cty's ElementIterator - if len(names) == 0 { - return cty.EmptyTupleVal, nil - } - keys = make([]cty.Value, len(names)) - for i, name := range names { - keys[i] = cty.StringVal(name) - } - return cty.TupleVal(keys), nil - default: - if !m.IsKnown() { - return cty.UnknownVal(retType), nil - } - - // cty guarantees that ElementIterator will iterate in lexicographical - // order by key. - for it := args[0].ElementIterator(); it.Next(); { - k, _ := it.Element() - keys = append(keys, k) - } - if len(keys) == 0 { - return cty.ListValEmpty(cty.String), nil - } - return cty.ListVal(keys), nil - } - }, -}) - // ListFunc constructs a function that takes an arbitrary number of arguments // and returns a list containing those values in the same order. // @@ -865,405 +459,6 @@ var MatchkeysFunc = function.New(&function.Spec{ }, }) -// MergeFunc constructs a function that takes an arbitrary number of maps or objects, and -// returns a single value that contains a merged set of keys and values from -// all of the inputs. -// -// If more than one given map or object defines the same key then the one that -// is later in the argument sequence takes precedence. -var MergeFunc = function.New(&function.Spec{ - Params: []function.Parameter{}, - VarParam: &function.Parameter{ - Name: "maps", - Type: cty.DynamicPseudoType, - AllowDynamicType: true, - AllowNull: true, - }, - Type: func(args []cty.Value) (cty.Type, error) { - // empty args is accepted, so assume an empty object since we have no - // key-value types. - if len(args) == 0 { - return cty.EmptyObject, nil - } - - // collect the possible object attrs - attrs := map[string]cty.Type{} - - first := cty.NilType - matching := true - attrsKnown := true - for i, arg := range args { - ty := arg.Type() - // any dynamic args mean we can't compute a type - if ty.Equals(cty.DynamicPseudoType) { - return cty.DynamicPseudoType, nil - } - - // check for invalid arguments - if !ty.IsMapType() && !ty.IsObjectType() { - return cty.NilType, fmt.Errorf("arguments must be maps or objects, got %#v", ty.FriendlyName()) - } - - switch { - case ty.IsObjectType() && !arg.IsNull(): - for attr, aty := range ty.AttributeTypes() { - attrs[attr] = aty - } - case ty.IsMapType(): - switch { - case arg.IsNull(): - // pass, nothing to add - case arg.IsKnown(): - ety := arg.Type().ElementType() - for it := arg.ElementIterator(); it.Next(); { - attr, _ := it.Element() - attrs[attr.AsString()] = ety - } - default: - // any unknown maps means we don't know all possible attrs - // for the return type - attrsKnown = false - } - } - - // record the first argument type for comparison - if i == 0 { - first = arg.Type() - continue - } - - if !ty.Equals(first) && matching { - matching = false - } - } - - // the types all match, so use the first argument type - if matching { - return first, nil - } - - // We had a mix of unknown maps and objects, so we can't predict the - // attributes - if !attrsKnown { - return cty.DynamicPseudoType, nil - } - - return cty.Object(attrs), nil - }, - Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { - outputMap := make(map[string]cty.Value) - - // if all inputs are null, return a null value rather than an object - // with null attributes - allNull := true - for _, arg := range args { - if arg.IsNull() { - continue - } else { - allNull = false - } - - for it := arg.ElementIterator(); it.Next(); { - k, v := it.Element() - outputMap[k.AsString()] = v - } - } - - switch { - case allNull: - return cty.NullVal(retType), nil - case retType.IsMapType(): - return cty.MapVal(outputMap), nil - case retType.IsObjectType(), retType.Equals(cty.DynamicPseudoType): - return cty.ObjectVal(outputMap), nil - default: - panic(fmt.Sprintf("unexpected return type: %#v", retType)) - } - }, -}) - -// ReverseFunc takes a sequence and produces a new sequence of the same length -// with all of the same elements as the given sequence but in reverse order. -var ReverseFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "list", - Type: cty.DynamicPseudoType, - }, - }, - Type: func(args []cty.Value) (cty.Type, error) { - argTy := args[0].Type() - switch { - case argTy.IsTupleType(): - argTys := argTy.TupleElementTypes() - retTys := make([]cty.Type, len(argTys)) - for i, ty := range argTys { - retTys[len(retTys)-i-1] = ty - } - return cty.Tuple(retTys), nil - case argTy.IsListType(), argTy.IsSetType(): // We accept sets here to mimic the usual behavior of auto-converting to list - return cty.List(argTy.ElementType()), nil - default: - return cty.NilType, function.NewArgErrorf(0, "can only reverse list or tuple values, not %s", argTy.FriendlyName()) - } - }, - Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { - in := args[0].AsValueSlice() - outVals := make([]cty.Value, len(in)) - for i, v := range in { - outVals[len(outVals)-i-1] = v - } - switch { - case retType.IsTupleType(): - return cty.TupleVal(outVals), nil - default: - if len(outVals) == 0 { - return cty.ListValEmpty(retType.ElementType()), nil - } - return cty.ListVal(outVals), nil - } - }, -}) - -// SetProductFunc calculates the Cartesian product of two or more sets or -// sequences. If the arguments are all lists then the result is a list of tuples, -// preserving the ordering of all of the input lists. Otherwise the result is a -// set of tuples. -var SetProductFunc = function.New(&function.Spec{ - Params: []function.Parameter{}, - VarParam: &function.Parameter{ - Name: "sets", - Type: cty.DynamicPseudoType, - }, - Type: func(args []cty.Value) (retType cty.Type, err error) { - if len(args) < 2 { - return cty.NilType, errors.New("at least two arguments are required") - } - - listCount := 0 - elemTys := make([]cty.Type, len(args)) - for i, arg := range args { - aty := arg.Type() - switch { - case aty.IsSetType(): - elemTys[i] = aty.ElementType() - case aty.IsListType(): - elemTys[i] = aty.ElementType() - listCount++ - case aty.IsTupleType(): - // We can accept a tuple type only if there's some common type - // that all of its elements can be converted to. - allEtys := aty.TupleElementTypes() - if len(allEtys) == 0 { - elemTys[i] = cty.DynamicPseudoType - listCount++ - break - } - ety, _ := convert.UnifyUnsafe(allEtys) - if ety == cty.NilType { - return cty.NilType, function.NewArgErrorf(i, "all elements must be of the same type") - } - elemTys[i] = ety - listCount++ - default: - return cty.NilType, function.NewArgErrorf(i, "a set or a list is required") - } - } - - if listCount == len(args) { - return cty.List(cty.Tuple(elemTys)), nil - } - return cty.Set(cty.Tuple(elemTys)), nil - }, - Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { - ety := retType.ElementType() - - total := 1 - for _, arg := range args { - // Because of our type checking function, we are guaranteed that - // all of the arguments are known, non-null values of types that - // support LengthInt. - total *= arg.LengthInt() - } - - if total == 0 { - // If any of the arguments was an empty collection then our result - // is also an empty collection, which we'll short-circuit here. - if retType.IsListType() { - return cty.ListValEmpty(ety), nil - } - return cty.SetValEmpty(ety), nil - } - - subEtys := ety.TupleElementTypes() - product := make([][]cty.Value, total) - - b := make([]cty.Value, total*len(args)) - n := make([]int, len(args)) - s := 0 - argVals := make([][]cty.Value, len(args)) - for i, arg := range args { - argVals[i] = arg.AsValueSlice() - } - - for i := range product { - e := s + len(args) - pi := b[s:e] - product[i] = pi - s = e - - for j, n := range n { - val := argVals[j][n] - ty := subEtys[j] - if !val.Type().Equals(ty) { - var err error - val, err = convert.Convert(val, ty) - if err != nil { - // Should never happen since we checked this in our - // type-checking function. - return cty.NilVal, fmt.Errorf("failed to convert argVals[%d][%d] to %s; this is a bug in Terraform", j, n, ty.FriendlyName()) - } - } - pi[j] = val - } - - for j := len(n) - 1; j >= 0; j-- { - n[j]++ - if n[j] < len(argVals[j]) { - break - } - n[j] = 0 - } - } - - productVals := make([]cty.Value, total) - for i, vals := range product { - productVals[i] = cty.TupleVal(vals) - } - - if retType.IsListType() { - return cty.ListVal(productVals), nil - } - return cty.SetVal(productVals), nil - }, -}) - -// SliceFunc constructs a function that extracts some consecutive elements -// from within a list. -var SliceFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "list", - Type: cty.DynamicPseudoType, - }, - { - Name: "start_index", - Type: cty.Number, - }, - { - Name: "end_index", - Type: cty.Number, - }, - }, - Type: func(args []cty.Value) (cty.Type, error) { - arg := args[0] - argTy := arg.Type() - - if argTy.IsSetType() { - return cty.NilType, function.NewArgErrorf(0, "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() && !argTy.IsTupleType() { - return cty.NilType, function.NewArgErrorf(0, "must be a list or tuple value") - } - - startIndex, endIndex, idxsKnown, err := sliceIndexes(args) - if err != nil { - return cty.NilType, err - } - - if argTy.IsListType() { - return argTy, nil - } - - if !idxsKnown { - // If we don't know our start/end indices then we can't predict - // the result type if we're planning to return a tuple. - return cty.DynamicPseudoType, nil - } - return cty.Tuple(argTy.TupleElementTypes()[startIndex:endIndex]), nil - }, - Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { - inputList := args[0] - - if retType == cty.DynamicPseudoType { - return cty.DynamicVal, nil - } - - // we ignore idxsKnown return value here because the indices are always - // known here, or else the call would've short-circuited. - startIndex, endIndex, _, err := sliceIndexes(args) - if err != nil { - return cty.NilVal, err - } - - if endIndex-startIndex == 0 { - if retType.IsTupleType() { - return cty.EmptyTupleVal, 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 - }, -}) - -func sliceIndexes(args []cty.Value) (int, int, bool, error) { - var startIndex, endIndex, length int - var startKnown, endKnown, lengthKnown bool - - if args[0].Type().IsTupleType() || args[0].IsKnown() { // if it's a tuple then we always know the length by the type, but lists must be known - length = args[0].LengthInt() - lengthKnown = true - } - - if args[1].IsKnown() { - if err := gocty.FromCtyValue(args[1], &startIndex); err != nil { - return 0, 0, false, function.NewArgErrorf(1, "invalid start index: %s", err) - } - if startIndex < 0 { - return 0, 0, false, function.NewArgErrorf(1, "start index must not be less than zero") - } - if lengthKnown && startIndex > length { - return 0, 0, false, function.NewArgErrorf(1, "start index must not be greater than the length of the list") - } - startKnown = true - } - if args[2].IsKnown() { - if err := gocty.FromCtyValue(args[2], &endIndex); err != nil { - return 0, 0, false, function.NewArgErrorf(2, "invalid end index: %s", err) - } - if endIndex < 0 { - return 0, 0, false, function.NewArgErrorf(2, "end index must not be less than zero") - } - if lengthKnown && endIndex > length { - return 0, 0, false, function.NewArgErrorf(2, "end index must not be greater than the length of the list") - } - endKnown = true - } - if startKnown && endKnown { - if startIndex > endIndex { - return 0, 0, false, function.NewArgErrorf(1, "start index must not be greater than end index") - } - } - return startIndex, endIndex, startKnown && endKnown, nil -} - // TransposeFunc constructs 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. var TransposeFunc = function.New(&function.Spec{ @@ -1318,152 +513,6 @@ var TransposeFunc = function.New(&function.Spec{ }, }) -// ValuesFunc constructs a function that returns a list of the map values, -// in the order of the sorted keys. -var ValuesFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "values", - Type: cty.DynamicPseudoType, - }, - }, - Type: func(args []cty.Value) (ret cty.Type, err error) { - ty := args[0].Type() - if ty.IsMapType() { - return cty.List(ty.ElementType()), nil - } else if ty.IsObjectType() { - // The result is a tuple type with all of the same types as our - // object type's attributes, sorted in lexicographical order by the - // keys. (This matches the sort order guaranteed by ElementIterator - // on a cty object value.) - atys := ty.AttributeTypes() - if len(atys) == 0 { - return cty.EmptyTuple, nil - } - attrNames := make([]string, 0, len(atys)) - for name := range atys { - attrNames = append(attrNames, name) - } - sort.Strings(attrNames) - - tys := make([]cty.Type, len(attrNames)) - for i, name := range attrNames { - tys[i] = atys[name] - } - return cty.Tuple(tys), nil - } - 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) { - mapVar := args[0] - - // We can just iterate the map/object value here because cty guarantees - // that these types always iterate in key lexicographical order. - var values []cty.Value - for it := mapVar.ElementIterator(); it.Next(); { - _, val := it.Element() - values = append(values, val) - } - - if retType.IsTupleType() { - return cty.TupleVal(values), nil - } - if len(values) == 0 { - return cty.ListValEmpty(retType.ElementType()), nil - } - return cty.ListVal(values), nil - }, -}) - -// ZipmapFunc constructs a function that constructs a map from a list of keys -// and a corresponding list of values. -var ZipmapFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "keys", - Type: cty.List(cty.String), - }, - { - Name: "values", - Type: cty.DynamicPseudoType, - }, - }, - Type: func(args []cty.Value) (ret cty.Type, err error) { - keys := args[0] - values := args[1] - valuesTy := values.Type() - - switch { - case valuesTy.IsListType(): - return cty.Map(values.Type().ElementType()), nil - case valuesTy.IsTupleType(): - if !keys.IsWhollyKnown() { - // Since zipmap with a tuple produces an object, we need to know - // all of the key names before we can predict our result type. - return cty.DynamicPseudoType, nil - } - - keysRaw := keys.AsValueSlice() - valueTypesRaw := valuesTy.TupleElementTypes() - if len(keysRaw) != len(valueTypesRaw) { - return cty.NilType, fmt.Errorf("number of keys (%d) does not match number of values (%d)", len(keysRaw), len(valueTypesRaw)) - } - atys := make(map[string]cty.Type, len(valueTypesRaw)) - for i, keyVal := range keysRaw { - if keyVal.IsNull() { - return cty.NilType, fmt.Errorf("keys list has null value at index %d", i) - } - key := keyVal.AsString() - atys[key] = valueTypesRaw[i] - } - return cty.Object(atys), nil - - default: - 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) { - keys := args[0] - values := args[1] - - if !keys.IsWhollyKnown() { - // Unknown map keys and object attributes are not supported, so - // our entire result must be unknown in this case. - return cty.UnknownVal(retType), nil - } - - // both keys and values are guaranteed to be shallowly-known here, - // because our declared params above don't allow unknown or null values. - if keys.LengthInt() != values.LengthInt() { - return cty.NilVal, fmt.Errorf("number of keys (%d) does not match number of values (%d)", keys.LengthInt(), values.LengthInt()) - } - - output := make(map[string]cty.Value) - - i := 0 - for it := keys.ElementIterator(); it.Next(); { - _, v := it.Element() - val := values.Index(cty.NumberIntVal(int64(i))) - output[v.AsString()] = val - i++ - } - - switch { - case retType.IsMapType(): - if len(output) == 0 { - return cty.MapValEmpty(retType.ElementType()), nil - } - return cty.MapVal(output), nil - case retType.IsObjectType(): - return cty.ObjectVal(output), nil - default: - // Should never happen because the type-check function should've - // caught any other case. - return cty.NilVal, fmt.Errorf("internally selected incorrect result type %s (this is a bug)", retType.FriendlyName()) - } - }, -}) - // helper function to add an element to a list, if it does not already exist func appendIfMissing(slice []cty.Value, element cty.Value) ([]cty.Value, error) { for _, ele := range slice { @@ -1478,13 +527,6 @@ func appendIfMissing(slice []cty.Value, element cty.Value) ([]cty.Value, error) return append(slice, element), nil } -// Element returns a single element from a given list at the given index. If -// index is greater than the length of the list then it is wrapped modulo -// the list length. -func Element(list, index cty.Value) (cty.Value, error) { - return ElementFunc.Call([]cty.Value{list, index}) -} - // Length returns the number of elements in the given collection or number of // Unicode characters in the given string. func Length(collection cty.Value) (cty.Value, error) { @@ -1496,49 +538,11 @@ func Coalesce(args ...cty.Value) (cty.Value, error) { return CoalesceFunc.Call(args) } -// CoalesceList takes any number of list arguments and returns the first one that isn't empty. -func CoalesceList(args ...cty.Value) (cty.Value, error) { - return CoalesceListFunc.Call(args) -} - -// Compact takes a list of strings and returns a new list -// with any empty string elements removed. -func Compact(list cty.Value) (cty.Value, error) { - return CompactFunc.Call([]cty.Value{list}) -} - -// Contains determines whether a given list contains a given single value -// as one of its elements. -func Contains(list, value cty.Value) (cty.Value, error) { - return ContainsFunc.Call([]cty.Value{list, value}) -} - // Index finds the element index for a given value in a list. func Index(list, value cty.Value) (cty.Value, error) { return IndexFunc.Call([]cty.Value{list, value}) } -// Distinct takes a list and returns a new list with any duplicate elements removed. -func Distinct(list cty.Value) (cty.Value, error) { - return DistinctFunc.Call([]cty.Value{list}) -} - -// Chunklist splits a single list into fixed-size chunks, returning a list of lists. -func Chunklist(list, size cty.Value) (cty.Value, error) { - return ChunklistFunc.Call([]cty.Value{list, size}) -} - -// Flatten takes a list and replaces any elements that are lists with a flattened -// sequence of the list contents. -func Flatten(list cty.Value) (cty.Value, error) { - return FlattenFunc.Call([]cty.Value{list}) -} - -// Keys takes a map and returns a sorted list of the map keys. -func Keys(inputMap cty.Value) (cty.Value, error) { - return KeysFunc.Call([]cty.Value{inputMap}) -} - // List takes any number of list arguments and returns a list containing those // values in the same order. func List(args ...cty.Value) (cty.Value, error) { @@ -1564,44 +568,8 @@ func Matchkeys(values, keys, searchset cty.Value) (cty.Value, error) { return MatchkeysFunc.Call([]cty.Value{values, keys, searchset}) } -// Merge takes an arbitrary number of maps and returns a single map that contains -// a merged set of elements from all of the maps. -// -// If more than one given map defines the same key then the one that is later in -// the argument sequence takes precedence. -func Merge(maps ...cty.Value) (cty.Value, error) { - return MergeFunc.Call(maps) -} - -// Reverse takes a sequence and produces a new sequence of the same length -// with all of the same elements as the given sequence but in reverse order. -func Reverse(list cty.Value) (cty.Value, error) { - return ReverseFunc.Call([]cty.Value{list}) -} - -// SetProduct computes the Cartesian product of sets or sequences. -func SetProduct(sets ...cty.Value) (cty.Value, error) { - return SetProductFunc.Call(sets) -} - -// Slice extracts some consecutive elements from within a list. -func Slice(list, start, end cty.Value) (cty.Value, error) { - return SliceFunc.Call([]cty.Value{list, start, end}) -} - // Transpose takes a map of lists of strings and swaps the keys and values to // produce a new map of lists of strings. func Transpose(values cty.Value) (cty.Value, error) { return TransposeFunc.Call([]cty.Value{values}) } - -// Values returns a list of the map values, in the order of the sorted keys. -// This function only works on flat maps. -func Values(values cty.Value) (cty.Value, error) { - return ValuesFunc.Call([]cty.Value{values}) -} - -// Zipmap constructs a map from a list of keys and a corresponding list of values. -func Zipmap(keys, values cty.Value) (cty.Value, error) { - return ZipmapFunc.Call([]cty.Value{keys, values}) -} diff --git a/lang/funcs/collection_test.go b/lang/funcs/collection_test.go index 4e437ab4d..76dd1dbf0 100644 --- a/lang/funcs/collection_test.go +++ b/lang/funcs/collection_test.go @@ -7,125 +7,6 @@ import ( "github.com/zclconf/go-cty/cty" ) -func TestElement(t *testing.T) { - tests := []struct { - List cty.Value - Index cty.Value - Want cty.Value - }{ - { - cty.ListVal([]cty.Value{ - cty.StringVal("hello"), - }), - cty.NumberIntVal(0), - cty.StringVal("hello"), - }, - { - cty.ListVal([]cty.Value{ - cty.StringVal("hello"), - }), - cty.NumberIntVal(1), - cty.StringVal("hello"), - }, - { - cty.ListVal([]cty.Value{ - cty.StringVal("hello"), - cty.StringVal("bonjour"), - }), - cty.NumberIntVal(0), - cty.StringVal("hello"), - }, - { - cty.ListVal([]cty.Value{ - cty.StringVal("hello"), - cty.StringVal("bonjour"), - }), - cty.NumberIntVal(1), - cty.StringVal("bonjour"), - }, - { - cty.ListVal([]cty.Value{ - cty.StringVal("hello"), - cty.StringVal("bonjour"), - }), - cty.NumberIntVal(2), - cty.StringVal("hello"), - }, - - { - cty.TupleVal([]cty.Value{ - cty.StringVal("hello"), - }), - cty.NumberIntVal(0), - cty.StringVal("hello"), - }, - { - cty.TupleVal([]cty.Value{ - cty.StringVal("hello"), - }), - cty.NumberIntVal(1), - cty.StringVal("hello"), - }, - { - cty.TupleVal([]cty.Value{ - cty.StringVal("hello"), - cty.StringVal("bonjour"), - }), - cty.NumberIntVal(0), - cty.StringVal("hello"), - }, - { - cty.TupleVal([]cty.Value{ - cty.StringVal("hello"), - cty.StringVal("bonjour"), - }), - cty.NumberIntVal(1), - cty.StringVal("bonjour"), - }, - { - cty.TupleVal([]cty.Value{ - cty.StringVal("hello"), - cty.StringVal("bonjour"), - }), - cty.NumberIntVal(2), - cty.StringVal("hello"), - }, - { - cty.TupleVal([]cty.Value{ - cty.StringVal("hello"), - cty.StringVal("bonjour"), - }), - cty.UnknownVal(cty.Number), - cty.DynamicVal, - }, - { - cty.UnknownVal(cty.Tuple([]cty.Type{cty.String, cty.Bool})), - cty.NumberIntVal(1), - cty.UnknownVal(cty.Bool), - }, - { - cty.UnknownVal(cty.Tuple([]cty.Type{cty.String, cty.String})), - cty.UnknownVal(cty.Number), - cty.DynamicVal, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("Element(%#v, %#v)", test.List, test.Index), func(t *testing.T) { - got, err := Element(test.List, test.Index) - - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } - -} - func TestLength(t *testing.T) { tests := []struct { Value cty.Value @@ -345,434 +226,6 @@ func TestCoalesce(t *testing.T) { } } -func TestCoalesceList(t *testing.T) { - tests := []struct { - Values []cty.Value - Want cty.Value - Err bool - }{ - { - []cty.Value{ - cty.ListVal([]cty.Value{ - cty.StringVal("first"), cty.StringVal("second"), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("third"), cty.StringVal("fourth"), - }), - }, - cty.ListVal([]cty.Value{ - cty.StringVal("first"), cty.StringVal("second"), - }), - false, - }, - { - []cty.Value{ - cty.ListValEmpty(cty.String), - cty.ListVal([]cty.Value{ - cty.StringVal("third"), cty.StringVal("fourth"), - }), - }, - cty.ListVal([]cty.Value{ - cty.StringVal("third"), cty.StringVal("fourth"), - }), - false, - }, - { - []cty.Value{ - cty.ListValEmpty(cty.Number), - cty.ListVal([]cty.Value{ - cty.NumberIntVal(1), - cty.NumberIntVal(2), - }), - }, - cty.ListVal([]cty.Value{ - cty.NumberIntVal(1), - cty.NumberIntVal(2), - }), - false, - }, - { // lists with mixed types - []cty.Value{ - cty.ListVal([]cty.Value{ - cty.StringVal("first"), cty.StringVal("second"), - }), - cty.ListVal([]cty.Value{ - cty.NumberIntVal(1), - cty.NumberIntVal(2), - }), - }, - cty.ListVal([]cty.Value{ - cty.StringVal("first"), cty.StringVal("second"), - }), - false, - }, - { // lists with mixed types - []cty.Value{ - cty.ListVal([]cty.Value{ - cty.NumberIntVal(1), - cty.NumberIntVal(2), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("first"), cty.StringVal("second"), - }), - }, - cty.ListVal([]cty.Value{ - cty.NumberIntVal(1), cty.NumberIntVal(2), - }), - false, - }, - { // list with unknown values - []cty.Value{ - cty.ListVal([]cty.Value{ - cty.StringVal("first"), cty.StringVal("second"), - }), - cty.ListVal([]cty.Value{ - cty.UnknownVal(cty.String), - }), - }, - cty.ListVal([]cty.Value{ - cty.StringVal("first"), cty.StringVal("second"), - }), - false, - }, - { // list with unknown values - []cty.Value{ - cty.ListVal([]cty.Value{ - cty.UnknownVal(cty.String), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("third"), cty.StringVal("fourth"), - }), - }, - cty.ListVal([]cty.Value{ - cty.UnknownVal(cty.String), - }), - false, - }, - { - []cty.Value{ - cty.MapValEmpty(cty.DynamicPseudoType), - cty.ListVal([]cty.Value{ - cty.StringVal("third"), cty.StringVal("fourth"), - }), - }, - cty.NilVal, - true, - }, - { // unknown list - []cty.Value{ - cty.ListVal([]cty.Value{ - cty.StringVal("third"), cty.StringVal("fourth"), - }), - cty.UnknownVal(cty.List(cty.String)), - }, - cty.ListVal([]cty.Value{ - cty.StringVal("third"), cty.StringVal("fourth"), - }), - false, - }, - { // unknown list - []cty.Value{ - cty.ListValEmpty(cty.String), - cty.UnknownVal(cty.List(cty.String)), - }, - cty.DynamicVal, - false, - }, - { // unknown list - []cty.Value{ - cty.UnknownVal(cty.List(cty.String)), - cty.ListVal([]cty.Value{ - cty.StringVal("third"), cty.StringVal("fourth"), - }), - }, - cty.DynamicVal, - 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 i, test := range tests { - t.Run(fmt.Sprintf("%d-coalescelist(%#v)", i, test.Values), func(t *testing.T) { - got, err := CoalesceList(test.Values...) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - -func TestCompact(t *testing.T) { - tests := []struct { - List cty.Value - Want cty.Value - Err bool - }{ - { - cty.ListVal([]cty.Value{ - cty.StringVal("test"), - cty.StringVal(""), - cty.StringVal("test"), - cty.NullVal(cty.String), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("test"), - cty.StringVal("test"), - }), - false, - }, - { - cty.ListVal([]cty.Value{ - cty.StringVal(""), - cty.StringVal(""), - cty.StringVal(""), - }), - cty.ListValEmpty(cty.String), - false, - }, - { - cty.ListVal([]cty.Value{ - cty.NullVal(cty.String), - cty.NullVal(cty.String), - }), - cty.ListValEmpty(cty.String), - false, - }, - { - cty.ListValEmpty(cty.String), - cty.ListValEmpty(cty.String), - false, - }, - { - cty.ListVal([]cty.Value{ - cty.StringVal("test"), - cty.StringVal("test"), - cty.StringVal(""), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("test"), - cty.StringVal("test"), - }), - false, - }, - { - cty.ListVal([]cty.Value{ - cty.StringVal("test"), - cty.UnknownVal(cty.String), - cty.StringVal(""), - cty.NullVal(cty.String), - }), - cty.UnknownVal(cty.List(cty.String)), - false, - }, - { // errors on list of lists - cty.ListVal([]cty.Value{ - cty.ListVal([]cty.Value{ - cty.StringVal("test"), - }), - cty.ListVal([]cty.Value{ - cty.StringVal(""), - }), - }), - cty.NilVal, - true, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("compact(%#v)", test.List), func(t *testing.T) { - got, err := Compact(test.List) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - -func TestContains(t *testing.T) { - listOfStrings := cty.ListVal([]cty.Value{ - cty.StringVal("the"), - cty.StringVal("quick"), - cty.StringVal("brown"), - cty.StringVal("fox"), - }) - listOfInts := cty.ListVal([]cty.Value{ - cty.NumberIntVal(1), - cty.NumberIntVal(2), - cty.NumberIntVal(3), - cty.NumberIntVal(4), - }) - listWithUnknown := cty.ListVal([]cty.Value{ - cty.StringVal("the"), - cty.StringVal("quick"), - cty.StringVal("brown"), - cty.UnknownVal(cty.String), - }) - - tests := []struct { - List cty.Value - Value cty.Value - Want cty.Value - Err bool - }{ - { - listOfStrings, - cty.StringVal("the"), - cty.BoolVal(true), - false, - }, - { - listWithUnknown, - cty.StringVal("the"), - cty.BoolVal(true), - false, - }, - { - listOfStrings, - cty.StringVal("penguin"), - cty.BoolVal(false), - false, - }, - { - listOfInts, - cty.NumberIntVal(1), - cty.BoolVal(true), - false, - }, - { - listOfInts, - cty.NumberIntVal(42), - cty.BoolVal(false), - false, - }, - { // And now we mix and match - listOfInts, - cty.StringVal("1"), - cty.BoolVal(false), - false, - }, - { // Check a list with an unknown value - cty.ListVal([]cty.Value{ - cty.UnknownVal(cty.String), - cty.StringVal("quick"), - cty.StringVal("brown"), - cty.StringVal("fox"), - }), - cty.StringVal("quick"), - cty.BoolVal(true), - 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 { - t.Run(fmt.Sprintf("contains(%#v, %#v)", test.List, test.Value), func(t *testing.T) { - got, err := Contains(test.List, test.Value) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - func TestIndex(t *testing.T) { tests := []struct { List cty.Value @@ -892,493 +345,6 @@ func TestIndex(t *testing.T) { } } -func TestDistinct(t *testing.T) { - tests := []struct { - List cty.Value - Want cty.Value - Err bool - }{ - { - cty.ListVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - cty.StringVal("a"), - cty.StringVal("b"), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - }), - false, - }, - { - cty.ListValEmpty(cty.String), - cty.ListValEmpty(cty.String), - false, - }, - { - cty.ListVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - cty.StringVal("a"), - cty.UnknownVal(cty.String), - }), - cty.UnknownVal(cty.List(cty.String)), - false, - }, - { - cty.ListVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - cty.StringVal("c"), - cty.StringVal("d"), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - cty.StringVal("c"), - cty.StringVal("d"), - }), - false, - }, - { - cty.ListVal([]cty.Value{ - cty.NumberIntVal(1), - cty.NumberIntVal(2), - cty.NumberIntVal(1), - cty.NumberIntVal(2), - }), - cty.ListVal([]cty.Value{ - cty.NumberIntVal(1), - cty.NumberIntVal(2), - }), - false, - }, - { - cty.ListVal([]cty.Value{ - cty.ListVal([]cty.Value{ - cty.NumberIntVal(1), - cty.NumberIntVal(2), - }), - cty.ListVal([]cty.Value{ - cty.NumberIntVal(1), - cty.NumberIntVal(2), - }), - }), - cty.ListVal([]cty.Value{ - cty.ListVal([]cty.Value{ - cty.NumberIntVal(1), - cty.NumberIntVal(2), - }), - }), - false, - }, - { - cty.ListVal([]cty.Value{ - cty.ListVal([]cty.Value{ - cty.NumberIntVal(1), - cty.NumberIntVal(2), - }), - cty.ListVal([]cty.Value{ - cty.NumberIntVal(3), - cty.NumberIntVal(4), - }), - }), - cty.ListVal([]cty.Value{ - cty.ListVal([]cty.Value{ - cty.NumberIntVal(1), - cty.NumberIntVal(2), - }), - cty.ListVal([]cty.Value{ - cty.NumberIntVal(3), - cty.NumberIntVal(4), - }), - }), - false, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("distinct(%#v)", test.List), func(t *testing.T) { - got, err := Distinct(test.List) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - -func TestChunklist(t *testing.T) { - tests := []struct { - List cty.Value - Size cty.Value - Want cty.Value - Err bool - }{ - { - cty.ListVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - cty.StringVal("c"), - }), - 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.StringVal("c"), - }), - }), - false, - }, - { - cty.ListVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - cty.StringVal("c"), - }), - cty.NumberIntVal(-1), - cty.NilVal, - true, - }, - { - cty.ListVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - cty.StringVal("c"), - }), - cty.NumberIntVal(0), - cty.ListVal([]cty.Value{ - cty.ListVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - cty.StringVal("c"), - }), - }), - false, - }, - { - cty.ListVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - cty.UnknownVal(cty.String), - }), - 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))), - false, - }, - { - cty.ListValEmpty(cty.String), - cty.NumberIntVal(3), - cty.ListValEmpty(cty.List(cty.String)), - false, - }, - } - - for i, test := range tests { - t.Run(fmt.Sprintf("%d-chunklist(%#v, %#v)", i, test.List, test.Size), func(t *testing.T) { - got, err := Chunklist(test.List, test.Size) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - -func TestFlatten(t *testing.T) { - tests := []struct { - List cty.Value - Want cty.Value - Err bool - }{ - { - cty.ListVal([]cty.Value{ - cty.ListVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("c"), - cty.StringVal("d"), - }), - }), - cty.TupleVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - cty.StringVal("c"), - cty.StringVal("d"), - }), - 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.StringVal("a"), - cty.StringVal("b"), - }), - cty.ListVal([]cty.Value{ - cty.UnknownVal(cty.String), - cty.StringVal("d"), - }), - }), - cty.TupleVal([]cty.Value{ - 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.EmptyTupleVal, - false, - }, - { - cty.SetVal([]cty.Value{ - cty.SetVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - }), - cty.SetVal([]cty.Value{ - cty.StringVal("c"), - cty.StringVal("d"), - }), - }), - cty.TupleVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - cty.StringVal("c"), - cty.StringVal("d"), - }), - false, - }, - { - cty.TupleVal([]cty.Value{ - cty.SetVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("c"), - cty.StringVal("d"), - }), - }), - cty.TupleVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - cty.StringVal("c"), - cty.StringVal("d"), - }), - false, - }, - } - - for i, test := range tests { - t.Run(fmt.Sprintf("%d-flatten(%#v)", i, test.List), func(t *testing.T) { - got, err := Flatten(test.List) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - -func TestKeys(t *testing.T) { - tests := []struct { - Map cty.Value - Want cty.Value - Err bool - }{ - { - cty.MapVal(map[string]cty.Value{ - "hello": cty.NumberIntVal(1), - "goodbye": cty.NumberIntVal(42), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("goodbye"), - cty.StringVal("hello"), - }), - false, - }, - { // same as above, but an object type - cty.ObjectVal(map[string]cty.Value{ - "hello": cty.NumberIntVal(1), - "goodbye": cty.StringVal("adieu"), - }), - cty.TupleVal([]cty.Value{ - cty.StringVal("goodbye"), - cty.StringVal("hello"), - }), - false, - }, - { // for an unknown object we can still return the keys, since they are part of the type - cty.UnknownVal(cty.Object(map[string]cty.Type{ - "hello": cty.Number, - "goodbye": cty.String, - })), - cty.TupleVal([]cty.Value{ - cty.StringVal("goodbye"), - cty.StringVal("hello"), - }), - false, - }, - { // an empty object has no keys - cty.EmptyObjectVal, - cty.EmptyTupleVal, - false, - }, - { // an empty map has no keys, but the result should still be properly typed - cty.MapValEmpty(cty.Number), - cty.ListValEmpty(cty.String), - false, - }, - { // Unknown map has unknown keys - cty.UnknownVal(cty.Map(cty.String)), - cty.UnknownVal(cty.List(cty.String)), - false, - }, - { // Not a map at all, so invalid - cty.StringVal("foo"), - cty.NilVal, - true, - }, - { // Can't get keys from a null object - cty.NullVal(cty.Object(map[string]cty.Type{ - "hello": cty.Number, - "goodbye": cty.String, - })), - cty.NilVal, - true, - }, - { // Can't get keys from a null map - cty.NullVal(cty.Map(cty.Number)), - cty.NilVal, - true, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("keys(%#v)", test.Map), func(t *testing.T) { - got, err := Keys(test.Map) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - func TestList(t *testing.T) { tests := []struct { Values []cty.Value @@ -2064,915 +1030,6 @@ func TestMatchkeys(t *testing.T) { } } -func TestMerge(t *testing.T) { - tests := []struct { - Values []cty.Value - Want cty.Value - Err bool - }{ - { - []cty.Value{ - cty.MapVal(map[string]cty.Value{ - "a": cty.StringVal("b"), - }), - cty.MapVal(map[string]cty.Value{ - "c": cty.StringVal("d"), - }), - }, - cty.MapVal(map[string]cty.Value{ - "a": cty.StringVal("b"), - "c": cty.StringVal("d"), - }), - false, - }, - { // handle unknowns - []cty.Value{ - cty.MapVal(map[string]cty.Value{ - "a": cty.UnknownVal(cty.String), - }), - cty.MapVal(map[string]cty.Value{ - "c": cty.StringVal("d"), - }), - }, - cty.MapVal(map[string]cty.Value{ - "a": cty.UnknownVal(cty.String), - "c": cty.StringVal("d"), - }), - false, - }, - { // handle null map - []cty.Value{ - cty.NullVal(cty.Map(cty.String)), - cty.MapVal(map[string]cty.Value{ - "c": cty.StringVal("d"), - }), - }, - cty.MapVal(map[string]cty.Value{ - "c": cty.StringVal("d"), - }), - false, - }, - { // handle null map - []cty.Value{ - cty.NullVal(cty.Map(cty.String)), - cty.NullVal(cty.Object(map[string]cty.Type{ - "a": cty.List(cty.String), - })), - }, - cty.NullVal(cty.EmptyObject), - false, - }, - { // handle null object - []cty.Value{ - cty.MapVal(map[string]cty.Value{ - "c": cty.StringVal("d"), - }), - cty.NullVal(cty.Object(map[string]cty.Type{ - "a": cty.List(cty.String), - })), - }, - cty.ObjectVal(map[string]cty.Value{ - "c": cty.StringVal("d"), - }), - false, - }, - { // handle unknowns - []cty.Value{ - cty.UnknownVal(cty.Map(cty.String)), - cty.MapVal(map[string]cty.Value{ - "c": cty.StringVal("d"), - }), - }, - cty.UnknownVal(cty.Map(cty.String)), - false, - }, - { // handle dynamic unknown - []cty.Value{ - cty.UnknownVal(cty.DynamicPseudoType), - cty.MapVal(map[string]cty.Value{ - "c": cty.StringVal("d"), - }), - }, - cty.DynamicVal, - false, - }, - { // merge with conflicts is ok, last in wins - []cty.Value{ - cty.MapVal(map[string]cty.Value{ - "a": cty.StringVal("b"), - "c": cty.StringVal("d"), - }), - cty.MapVal(map[string]cty.Value{ - "a": cty.StringVal("x"), - }), - }, - cty.MapVal(map[string]cty.Value{ - "a": cty.StringVal("x"), - "c": cty.StringVal("d"), - }), - false, - }, - { // only accept maps - []cty.Value{ - cty.MapVal(map[string]cty.Value{ - "a": cty.StringVal("b"), - "c": cty.StringVal("d"), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("x"), - }), - }, - cty.NilVal, - true, - }, - - { // argument error, for a null type - []cty.Value{ - cty.MapVal(map[string]cty.Value{ - "a": cty.StringVal("b"), - }), - cty.NullVal(cty.String), - }, - cty.NilVal, - true, - }, - { // merge maps of maps - []cty.Value{ - cty.MapVal(map[string]cty.Value{ - "a": cty.MapVal(map[string]cty.Value{ - "b": cty.StringVal("c"), - }), - }), - cty.MapVal(map[string]cty.Value{ - "d": cty.MapVal(map[string]cty.Value{ - "e": cty.StringVal("f"), - }), - }), - }, - cty.MapVal(map[string]cty.Value{ - "a": cty.MapVal(map[string]cty.Value{ - "b": cty.StringVal("c"), - }), - "d": cty.MapVal(map[string]cty.Value{ - "e": cty.StringVal("f"), - }), - }), - false, - }, - { // map of lists - []cty.Value{ - cty.MapVal(map[string]cty.Value{ - "a": cty.ListVal([]cty.Value{ - cty.StringVal("b"), - cty.StringVal("c"), - }), - }), - cty.MapVal(map[string]cty.Value{ - "d": cty.ListVal([]cty.Value{ - cty.StringVal("e"), - cty.StringVal("f"), - }), - }), - }, - cty.MapVal(map[string]cty.Value{ - "a": cty.ListVal([]cty.Value{ - cty.StringVal("b"), - cty.StringVal("c"), - }), - "d": cty.ListVal([]cty.Value{ - cty.StringVal("e"), - cty.StringVal("f"), - }), - }), - false, - }, - { // merge map of various kinds - []cty.Value{ - cty.MapVal(map[string]cty.Value{ - "a": cty.ListVal([]cty.Value{ - cty.StringVal("b"), - cty.StringVal("c"), - }), - }), - cty.MapVal(map[string]cty.Value{ - "d": cty.MapVal(map[string]cty.Value{ - "e": cty.StringVal("f"), - }), - }), - }, - cty.ObjectVal(map[string]cty.Value{ - "a": cty.ListVal([]cty.Value{ - cty.StringVal("b"), - cty.StringVal("c"), - }), - "d": cty.MapVal(map[string]cty.Value{ - "e": cty.StringVal("f"), - }), - }), - false, - }, - { // merge objects of various shapes - []cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "a": cty.ListVal([]cty.Value{ - cty.StringVal("b"), - }), - }), - cty.ObjectVal(map[string]cty.Value{ - "d": cty.DynamicVal, - }), - }, - cty.ObjectVal(map[string]cty.Value{ - "a": cty.ListVal([]cty.Value{ - cty.StringVal("b"), - }), - "d": cty.DynamicVal, - }), - false, - }, - { // merge maps and objects - []cty.Value{ - cty.MapVal(map[string]cty.Value{ - "a": cty.ListVal([]cty.Value{ - cty.StringVal("b"), - }), - }), - cty.ObjectVal(map[string]cty.Value{ - "d": cty.NumberIntVal(2), - }), - }, - cty.ObjectVal(map[string]cty.Value{ - "a": cty.ListVal([]cty.Value{ - cty.StringVal("b"), - }), - "d": cty.NumberIntVal(2), - }), - false, - }, - { // attr a type and value is overridden - []cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "a": cty.ListVal([]cty.Value{ - cty.StringVal("b"), - }), - "b": cty.StringVal("b"), - }), - cty.ObjectVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "e": cty.StringVal("f"), - }), - }), - }, - cty.ObjectVal(map[string]cty.Value{ - "a": cty.ObjectVal(map[string]cty.Value{ - "e": cty.StringVal("f"), - }), - "b": cty.StringVal("b"), - }), - false, - }, - { // argument error: non map type - []cty.Value{ - cty.MapVal(map[string]cty.Value{ - "a": cty.ListVal([]cty.Value{ - cty.StringVal("b"), - cty.StringVal("c"), - }), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("d"), - cty.StringVal("e"), - }), - }, - cty.NilVal, - true, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("merge(%#v)", test.Values), func(t *testing.T) { - got, err := Merge(test.Values...) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - -func TestReverse(t *testing.T) { - tests := []struct { - List cty.Value - Want cty.Value - Err string - }{ - { - cty.ListValEmpty(cty.String), - cty.ListValEmpty(cty.String), - "", - }, - { - cty.ListVal([]cty.Value{cty.StringVal("a")}), - cty.ListVal([]cty.Value{cty.StringVal("a")}), - "", - }, - { - cty.ListVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")}), - cty.ListVal([]cty.Value{cty.StringVal("b"), cty.StringVal("a")}), - "", - }, - { - cty.ListVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b"), cty.StringVal("c")}), - cty.ListVal([]cty.Value{cty.StringVal("c"), cty.StringVal("b"), cty.StringVal("a")}), - "", - }, - { - cty.ListVal([]cty.Value{cty.UnknownVal(cty.String), cty.StringVal("b"), cty.StringVal("c")}), - cty.ListVal([]cty.Value{cty.StringVal("c"), cty.StringVal("b"), cty.UnknownVal(cty.String)}), - "", - }, - { - cty.EmptyTupleVal, - cty.EmptyTupleVal, - "", - }, - { - cty.TupleVal([]cty.Value{cty.StringVal("a")}), - cty.TupleVal([]cty.Value{cty.StringVal("a")}), - "", - }, - { - cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.True}), - cty.TupleVal([]cty.Value{cty.True, cty.StringVal("a")}), - "", - }, - { - cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.True, cty.Zero}), - cty.TupleVal([]cty.Value{cty.Zero, cty.True, cty.StringVal("a")}), - "", - }, - { - cty.SetValEmpty(cty.String), - cty.ListValEmpty(cty.String), - "", - }, - { - cty.SetVal([]cty.Value{cty.StringVal("a")}), - cty.ListVal([]cty.Value{cty.StringVal("a")}), - "", - }, - { - cty.SetVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")}), - cty.ListVal([]cty.Value{cty.StringVal("b"), cty.StringVal("a")}), // set-of-string iterates in lexicographical order - "", - }, - { - cty.SetVal([]cty.Value{cty.StringVal("b"), cty.StringVal("a"), cty.StringVal("c")}), - cty.ListVal([]cty.Value{cty.StringVal("c"), cty.StringVal("b"), cty.StringVal("a")}), // set-of-string iterates in lexicographical order - "", - }, - { - cty.StringVal("no"), - cty.NilVal, - "can only reverse list or tuple values, not string", - }, - { - cty.True, - cty.NilVal, - "can only reverse list or tuple values, not bool", - }, - { - cty.MapValEmpty(cty.String), - cty.NilVal, - "can only reverse list or tuple values, not map of string", - }, - { - cty.NullVal(cty.List(cty.String)), - cty.NilVal, - "argument must not be null", - }, - { - cty.UnknownVal(cty.List(cty.String)), - cty.UnknownVal(cty.List(cty.String)), - "", - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("reverse(%#v)", test.List), func(t *testing.T) { - got, err := Reverse(test.List) - - if test.Err != "" { - if err == nil { - t.Fatal("succeeded; want error") - } - if got, want := err.Error(), test.Err; got != want { - t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } - -} - -func TestSetProduct(t *testing.T) { - tests := []struct { - Sets []cty.Value - Want cty.Value - Err string - }{ - { - nil, - cty.DynamicVal, - "at least two arguments are required", - }, - { - []cty.Value{ - cty.SetValEmpty(cty.String), - }, - cty.DynamicVal, - "at least two arguments are required", - }, - { - []cty.Value{ - cty.SetValEmpty(cty.String), - cty.StringVal("hello"), - }, - cty.DynamicVal, - "a set or a list is required", // this is an ArgError, so is presented against the second argument in particular - }, - { - []cty.Value{ - cty.SetValEmpty(cty.String), - cty.SetValEmpty(cty.String), - }, - cty.SetValEmpty(cty.Tuple([]cty.Type{cty.String, cty.String})), - "", - }, - { - []cty.Value{ - cty.SetVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}), - cty.SetVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}), - }, - cty.SetVal([]cty.Value{ - cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("bar")}), - cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("bar")}), - cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("bar")}), - }), - "", - }, - { - []cty.Value{ - cty.ListVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}), - cty.SetVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}), - }, - cty.SetVal([]cty.Value{ - cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("bar")}), - cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("bar")}), - cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("bar")}), - }), - "", - }, - { - []cty.Value{ - cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}), - cty.SetVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}), - }, - cty.SetVal([]cty.Value{ - cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("bar")}), - cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("bar")}), - cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("bar")}), - }), - "", - }, - { - []cty.Value{ - cty.ListVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}), - cty.ListVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}), - }, - cty.ListVal([]cty.Value{ - cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("bar")}), - cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("bar")}), - cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("bar")}), - }), - "", - }, - { - []cty.Value{ - cty.ListVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}), - cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}), - }, - cty.ListVal([]cty.Value{ - cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("bar")}), - cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("bar")}), - cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("bar")}), - }), - "", - }, - { - []cty.Value{ - cty.ListVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}), - cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.True}), - }, - cty.ListVal([]cty.Value{ - cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("true")}), - cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("true")}), - cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("true")}), - }), - "", - }, - { - []cty.Value{ - cty.ListVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}), - cty.EmptyTupleVal, - }, - cty.ListValEmpty(cty.Tuple([]cty.Type{cty.String, cty.DynamicPseudoType})), - "", - }, - { - []cty.Value{ - cty.ListVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}), - cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.EmptyObjectVal}), - }, - cty.DynamicVal, - "all elements must be of the same type", // this is an ArgError for the second argument - }, - { - []cty.Value{ - cty.SetVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}), - cty.SetVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}), - cty.SetVal([]cty.Value{cty.StringVal("baz")}), - }, - cty.SetVal([]cty.Value{ - cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("foo"), cty.StringVal("baz")}), - cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("foo"), cty.StringVal("baz")}), - cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("foo"), cty.StringVal("baz")}), - cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("bar"), cty.StringVal("baz")}), - cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("bar"), cty.StringVal("baz")}), - cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("bar"), cty.StringVal("baz")}), - }), - "", - }, - { - []cty.Value{ - cty.SetVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}), - cty.SetValEmpty(cty.String), - }, - cty.SetValEmpty(cty.Tuple([]cty.Type{cty.String, cty.String})), - "", - }, - { - []cty.Value{ - cty.SetVal([]cty.Value{cty.StringVal("foo")}), - cty.SetVal([]cty.Value{cty.StringVal("bar")}), - }, - cty.SetVal([]cty.Value{ - cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}), - }), - "", - }, - { - []cty.Value{ - cty.TupleVal([]cty.Value{cty.StringVal("foo")}), - cty.TupleVal([]cty.Value{cty.StringVal("bar")}), - }, - cty.ListVal([]cty.Value{ - cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}), - }), - "", - }, - { - []cty.Value{ - cty.SetVal([]cty.Value{cty.StringVal("foo")}), - cty.SetVal([]cty.Value{cty.DynamicVal}), - }, - cty.SetVal([]cty.Value{ - cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.DynamicVal}), - }), - "", - }, - { - []cty.Value{ - cty.SetVal([]cty.Value{cty.StringVal("foo")}), - cty.SetVal([]cty.Value{cty.True, cty.DynamicVal}), - }, - cty.SetVal([]cty.Value{ - cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.True}), - cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.UnknownVal(cty.Bool)}), - }), - "", - }, - { - []cty.Value{ - cty.UnknownVal(cty.Set(cty.String)), - cty.SetVal([]cty.Value{cty.True, cty.False}), - }, - cty.UnknownVal(cty.Set(cty.Tuple([]cty.Type{cty.String, cty.Bool}))), - "", - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("setproduct(%#v)", test.Sets), func(t *testing.T) { - got, err := SetProduct(test.Sets...) - - if test.Err != "" { - if err == nil { - t.Fatal("succeeded; want error") - } - if got, want := err.Error(), test.Err; got != want { - t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } - -} - -func TestSlice(t *testing.T) { - listOfStrings := cty.ListVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - }) - listOfInts := cty.ListVal([]cty.Value{ - cty.NumberIntVal(1), - cty.NumberIntVal(2), - }) - listWithUnknowns := cty.ListVal([]cty.Value{ - cty.StringVal("a"), - cty.UnknownVal(cty.String), - }) - tuple := cty.TupleVal([]cty.Value{ - cty.StringVal("a"), - cty.NumberIntVal(1), - cty.UnknownVal(cty.List(cty.String)), - }) - tests := []struct { - List cty.Value - StartIndex cty.Value - EndIndex cty.Value - Want cty.Value - Err bool - }{ - { // normal usage - listOfStrings, - cty.NumberIntVal(1), - cty.NumberIntVal(2), - cty.ListVal([]cty.Value{ - cty.StringVal("b"), - }), - false, - }, - { // slice only an unknown value - listWithUnknowns, - cty.NumberIntVal(1), - 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)), - false, - }, - { // normal usage - listOfInts, - cty.NumberIntVal(1), - cty.NumberIntVal(2), - cty.ListVal([]cty.Value{ - cty.NumberIntVal(2), - }), - false, - }, - { // empty result - listOfStrings, - cty.NumberIntVal(1), - cty.NumberIntVal(1), - cty.ListValEmpty(cty.String), - false, - }, - { // index out of bounds - listOfStrings, - cty.NumberIntVal(1), - cty.NumberIntVal(4), - cty.NilVal, - true, - }, - { // StartIndex index > EndIndex - listOfStrings, - cty.NumberIntVal(2), - cty.NumberIntVal(1), - cty.NilVal, - true, - }, - { // negative StartIndex - listOfStrings, - cty.NumberIntVal(-1), - cty.NumberIntVal(0), - cty.NilVal, - 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, - }, - { // unknown tuple slice - cty.UnknownVal(tuple.Type()), - cty.NumberIntVal(1), - cty.NumberIntVal(3), - cty.UnknownVal(cty.Tuple([]cty.Type{ - cty.Number, - 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, - }, - { // list with unknown start offset - listOfStrings, - cty.UnknownVal(cty.Number), - cty.NumberIntVal(2), - cty.UnknownVal(cty.List(cty.String)), - false, - }, - { // list with unknown start offset but end out of bounds - listOfStrings, - cty.UnknownVal(cty.Number), - cty.NumberIntVal(200), - cty.UnknownVal(cty.List(cty.String)), - true, - }, - { // list with unknown start offset but end < 0 - listOfStrings, - cty.UnknownVal(cty.Number), - cty.NumberIntVal(-4), - cty.UnknownVal(cty.List(cty.String)), - true, - }, - { // list with unknown end offset - listOfStrings, - cty.UnknownVal(cty.Number), - cty.NumberIntVal(0), - cty.UnknownVal(cty.List(cty.String)), - false, - }, - { // list with unknown end offset but start out of bounds - listOfStrings, - cty.UnknownVal(cty.Number), - cty.NumberIntVal(200), - cty.UnknownVal(cty.List(cty.String)), - true, - }, - { // list with unknown end offset but start < 0 - listOfStrings, - cty.UnknownVal(cty.Number), - cty.NumberIntVal(-3), - cty.UnknownVal(cty.List(cty.String)), - true, - }, - { // tuple slice with unknown start offset - tuple, - cty.UnknownVal(cty.Number), - cty.NumberIntVal(3), - cty.DynamicVal, - false, - }, - { // tuple slice with unknown start offset but end out of bounds - tuple, - cty.UnknownVal(cty.Number), - cty.NumberIntVal(200), - cty.DynamicVal, - true, - }, - { // tuple slice with unknown start offset but end < 0 - tuple, - cty.UnknownVal(cty.Number), - cty.NumberIntVal(-20), - cty.DynamicVal, - true, - }, - { // tuple slice with unknown end offset - tuple, - cty.NumberIntVal(0), - cty.UnknownVal(cty.Number), - cty.DynamicVal, - false, - }, - { // tuple slice with unknown end offset but start < 0 - tuple, - cty.NumberIntVal(-2), - cty.UnknownVal(cty.Number), - cty.DynamicVal, - true, - }, - { // tuple slice with unknown end offset but start out of bounds - tuple, - cty.NumberIntVal(200), - cty.UnknownVal(cty.Number), - cty.DynamicVal, - true, - }, - } - - for i, test := range tests { - 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) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - func TestTranspose(t *testing.T) { tests := []struct { Values cty.Value @@ -3053,286 +1110,3 @@ func TestTranspose(t *testing.T) { }) } } - -func TestValues(t *testing.T) { - tests := []struct { - Values cty.Value - Want cty.Value - Err bool - }{ - { - cty.MapVal(map[string]cty.Value{ - "hello": cty.StringVal("world"), - "what's": cty.StringVal("up"), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("world"), - cty.StringVal("up"), - }), - false, - }, - { - cty.ObjectVal(map[string]cty.Value{ - "what's": cty.StringVal("up"), - "hello": cty.StringVal("world"), - }), - cty.TupleVal([]cty.Value{ - cty.StringVal("world"), - cty.StringVal("up"), - }), - false, - }, - { // empty object - cty.EmptyObjectVal, - cty.EmptyTupleVal, - false, - }, - { - cty.UnknownVal(cty.Object(map[string]cty.Type{ - "what's": cty.String, - "hello": cty.Bool, - })), - cty.UnknownVal(cty.Tuple([]cty.Type{ - cty.Bool, - cty.String, - })), - false, - }, - { // note ordering: keys are sorted first - cty.MapVal(map[string]cty.Value{ - "hello": cty.NumberIntVal(1), - "goodbye": cty.NumberIntVal(42), - }), - cty.ListVal([]cty.Value{ - cty.NumberIntVal(42), - cty.NumberIntVal(1), - }), - false, - }, - { // map of lists - cty.MapVal(map[string]cty.Value{ - "hello": cty.ListVal([]cty.Value{cty.StringVal("world")}), - "what's": cty.ListVal([]cty.Value{cty.StringVal("up")}), - }), - cty.ListVal([]cty.Value{ - cty.ListVal([]cty.Value{cty.StringVal("world")}), - cty.ListVal([]cty.Value{cty.StringVal("up")}), - }), - false, - }, - { // map with unknowns - cty.MapVal(map[string]cty.Value{ - "hello": cty.ListVal([]cty.Value{cty.StringVal("world")}), - "what's": cty.UnknownVal(cty.List(cty.String)), - }), - cty.ListVal([]cty.Value{ - cty.ListVal([]cty.Value{cty.StringVal("world")}), - cty.UnknownVal(cty.List(cty.String)), - }), - false, - }, - { // empty m - cty.MapValEmpty(cty.DynamicPseudoType), - cty.ListValEmpty(cty.DynamicPseudoType), - false, - }, - { // unknown m - cty.UnknownVal(cty.Map(cty.String)), - cty.UnknownVal(cty.List(cty.String)), - false, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("values(%#v)", test.Values), func(t *testing.T) { - got, err := Values(test.Values) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - -func TestZipmap(t *testing.T) { - list1 := cty.ListVal([]cty.Value{ - cty.StringVal("hello"), - cty.StringVal("world"), - }) - list2 := cty.ListVal([]cty.Value{ - cty.StringVal("bar"), - cty.StringVal("baz"), - }) - list3 := cty.ListVal([]cty.Value{ - cty.StringVal("hello"), - cty.StringVal("there"), - cty.StringVal("world"), - }) - list4 := cty.ListVal([]cty.Value{ - cty.NumberIntVal(1), - cty.NumberIntVal(42), - }) - list5 := cty.ListVal([]cty.Value{ - cty.ListVal([]cty.Value{ - cty.StringVal("bar"), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("baz"), - }), - }) - tests := []struct { - Keys cty.Value - Values cty.Value - Want cty.Value - Err bool - }{ - { - list1, - list2, - cty.MapVal(map[string]cty.Value{ - "hello": cty.StringVal("bar"), - "world": cty.StringVal("baz"), - }), - false, - }, - { - list1, - list4, - cty.MapVal(map[string]cty.Value{ - "hello": cty.NumberIntVal(1), - "world": cty.NumberIntVal(42), - }), - false, - }, - { // length mismatch - list1, - list3, - cty.NilVal, - true, - }, - { // map of lists - list1, - list5, - cty.MapVal(map[string]cty.Value{ - "hello": cty.ListVal([]cty.Value{cty.StringVal("bar")}), - "world": cty.ListVal([]cty.Value{cty.StringVal("baz")}), - }), - false, - }, - { // tuple values produce object - cty.ListVal([]cty.Value{ - cty.StringVal("hello"), - cty.StringVal("world"), - }), - cty.TupleVal([]cty.Value{ - cty.StringVal("bar"), - cty.UnknownVal(cty.Bool), - }), - cty.ObjectVal(map[string]cty.Value{ - "hello": cty.StringVal("bar"), - "world": cty.UnknownVal(cty.Bool), - }), - false, - }, - { // empty tuple produces empty object - cty.ListValEmpty(cty.String), - cty.EmptyTupleVal, - cty.EmptyObjectVal, - false, - }, - { // tuple with any unknown keys produces DynamicVal - cty.ListVal([]cty.Value{ - cty.StringVal("hello"), - cty.UnknownVal(cty.String), - }), - cty.TupleVal([]cty.Value{ - cty.StringVal("bar"), - cty.True, - }), - cty.DynamicVal, - false, - }, - { // tuple with all keys unknown produces DynamicVal - cty.UnknownVal(cty.List(cty.String)), - cty.TupleVal([]cty.Value{ - cty.StringVal("bar"), - cty.True, - }), - cty.DynamicVal, - false, - }, - { // list with all keys unknown produces correctly-typed unknown map - cty.UnknownVal(cty.List(cty.String)), - cty.ListVal([]cty.Value{ - cty.StringVal("bar"), - cty.StringVal("baz"), - }), - cty.UnknownVal(cty.Map(cty.String)), - false, - }, - { // unknown tuple as values produces correctly-typed unknown object - cty.ListVal([]cty.Value{ - cty.StringVal("hello"), - cty.StringVal("world"), - }), - cty.UnknownVal(cty.Tuple([]cty.Type{ - cty.String, - cty.Bool, - })), - cty.UnknownVal(cty.Object(map[string]cty.Type{ - "hello": cty.String, - "world": cty.Bool, - })), - false, - }, - { // unknown list as values produces correctly-typed unknown map - cty.ListVal([]cty.Value{ - cty.StringVal("hello"), - cty.StringVal("world"), - }), - cty.UnknownVal(cty.List(cty.String)), - cty.UnknownVal(cty.Map(cty.String)), - false, - }, - { // empty input returns an empty map - cty.ListValEmpty(cty.String), - cty.ListValEmpty(cty.String), - cty.MapValEmpty(cty.String), - false, - }, - { // keys cannot be a list of lists - list5, - list1, - cty.NilVal, - true, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("zipmap(%#v, %#v)", test.Keys, test.Values), func(t *testing.T) { - got, err := Zipmap(test.Keys, test.Values) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\n\nkeys: %#v\nvalues: %#v\ngot: %#v\nwant: %#v", test.Keys, test.Values, got, test.Want) - } - }) - } -} diff --git a/lang/funcs/filesystem_test.go b/lang/funcs/filesystem_test.go index 73428e896..36785be90 100644 --- a/lang/funcs/filesystem_test.go +++ b/lang/funcs/filesystem_test.go @@ -8,6 +8,7 @@ import ( homedir "github.com/mitchellh/go-homedir" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" + "github.com/zclconf/go-cty/cty/function/stdlib" ) func TestFile(t *testing.T) { @@ -150,7 +151,7 @@ func TestTemplateFile(t *testing.T) { templateFileFn := MakeTemplateFileFunc(".", func() map[string]function.Function { return map[string]function.Function{ - "join": JoinFunc, + "join": stdlib.JoinFunc, "templatefile": MakeFileFunc(".", false), // just a placeholder, since templatefile itself overrides this } }) diff --git a/lang/funcs/number.go b/lang/funcs/number.go index c813f47bf..43effec12 100644 --- a/lang/funcs/number.go +++ b/lang/funcs/number.go @@ -9,44 +9,6 @@ import ( "github.com/zclconf/go-cty/cty/gocty" ) -// CeilFunc contructs a function that returns the closest whole number greater -// than or equal to the given value. -var CeilFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "num", - Type: cty.Number, - }, - }, - Type: function.StaticReturnType(cty.Number), - Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { - var val float64 - if err := gocty.FromCtyValue(args[0], &val); err != nil { - return cty.UnknownVal(cty.String), err - } - return cty.NumberIntVal(int64(math.Ceil(val))), nil - }, -}) - -// FloorFunc contructs a function that returns the closest whole number lesser -// than or equal to the given value. -var FloorFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "num", - Type: cty.Number, - }, - }, - Type: function.StaticReturnType(cty.Number), - Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { - var val float64 - if err := gocty.FromCtyValue(args[0], &val); err != nil { - return cty.UnknownVal(cty.String), err - } - return cty.NumberIntVal(int64(math.Floor(val))), nil - }, -}) - // LogFunc contructs a function that returns the logarithm of a given number in a given base. var LogFunc = function.New(&function.Spec{ Params: []function.Parameter{ @@ -185,16 +147,6 @@ var ParseIntFunc = function.New(&function.Spec{ }, }) -// Ceil returns the closest whole number greater than or equal to the given value. -func Ceil(num cty.Value) (cty.Value, error) { - return CeilFunc.Call([]cty.Value{num}) -} - -// Floor returns the closest whole number lesser than or equal to the given value. -func Floor(num cty.Value) (cty.Value, error) { - return FloorFunc.Call([]cty.Value{num}) -} - // Log returns returns the logarithm of a given number in a given base. func Log(num, base cty.Value) (cty.Value, error) { return LogFunc.Call([]cty.Value{num, base}) diff --git a/lang/funcs/number_test.go b/lang/funcs/number_test.go index 97ec70a75..b467a429f 100644 --- a/lang/funcs/number_test.go +++ b/lang/funcs/number_test.go @@ -7,82 +7,6 @@ import ( "github.com/zclconf/go-cty/cty" ) -func TestCeil(t *testing.T) { - tests := []struct { - Num cty.Value - Want cty.Value - Err bool - }{ - { - cty.NumberFloatVal(-1.8), - cty.NumberFloatVal(-1), - false, - }, - { - cty.NumberFloatVal(1.2), - cty.NumberFloatVal(2), - false, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("ceil(%#v)", test.Num), func(t *testing.T) { - got, err := Ceil(test.Num) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - -func TestFloor(t *testing.T) { - tests := []struct { - Num cty.Value - Want cty.Value - Err bool - }{ - { - cty.NumberFloatVal(-1.8), - cty.NumberFloatVal(-2), - false, - }, - { - cty.NumberFloatVal(1.2), - cty.NumberFloatVal(1), - false, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("floor(%#v)", test.Num), func(t *testing.T) { - got, err := Floor(test.Num) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - func TestLog(t *testing.T) { tests := []struct { Num cty.Value diff --git a/lang/funcs/string.go b/lang/funcs/string.go index 2e66be451..ab6da7277 100644 --- a/lang/funcs/string.go +++ b/lang/funcs/string.go @@ -1,168 +1,13 @@ package funcs import ( - "fmt" "regexp" - "sort" "strings" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" - "github.com/zclconf/go-cty/cty/gocty" ) -var JoinFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "separator", - Type: cty.String, - }, - }, - VarParam: &function.Parameter{ - Name: "lists", - Type: cty.List(cty.String), - }, - Type: function.StaticReturnType(cty.String), - Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { - sep := args[0].AsString() - listVals := args[1:] - if len(listVals) < 1 { - return cty.UnknownVal(cty.String), fmt.Errorf("at least one list is required") - } - - l := 0 - for _, list := range listVals { - if !list.IsWhollyKnown() { - return cty.UnknownVal(cty.String), nil - } - l += list.LengthInt() - } - - items := make([]string, 0, l) - for ai, list := range listVals { - ei := 0 - for it := list.ElementIterator(); it.Next(); { - _, val := it.Element() - if val.IsNull() { - if len(listVals) > 1 { - return cty.UnknownVal(cty.String), function.NewArgErrorf(ai+1, "element %d of list %d is null; cannot concatenate null values", ei, ai+1) - } - return cty.UnknownVal(cty.String), function.NewArgErrorf(ai+1, "element %d is null; cannot concatenate null values", ei) - } - items = append(items, val.AsString()) - ei++ - } - } - - return cty.StringVal(strings.Join(items, sep)), nil - }, -}) - -var SortFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "list", - Type: cty.List(cty.String), - }, - }, - Type: function.StaticReturnType(cty.List(cty.String)), - Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { - listVal := args[0] - - if !listVal.IsWhollyKnown() { - // If some of the element values aren't known yet then we - // can't yet predict the order of the result. - return cty.UnknownVal(retType), nil - } - if listVal.LengthInt() == 0 { // Easy path - return listVal, nil - } - - list := make([]string, 0, listVal.LengthInt()) - for it := listVal.ElementIterator(); it.Next(); { - iv, v := it.Element() - if v.IsNull() { - return cty.UnknownVal(retType), fmt.Errorf("given list element %s is null; a null string cannot be sorted", iv.AsBigFloat().String()) - } - list = append(list, v.AsString()) - } - - sort.Strings(list) - retVals := make([]cty.Value, len(list)) - for i, s := range list { - retVals[i] = cty.StringVal(s) - } - return cty.ListVal(retVals), nil - }, -}) - -var SplitFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "separator", - Type: cty.String, - }, - { - Name: "str", - Type: cty.String, - }, - }, - Type: function.StaticReturnType(cty.List(cty.String)), - Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { - sep := args[0].AsString() - str := args[1].AsString() - elems := strings.Split(str, sep) - elemVals := make([]cty.Value, len(elems)) - for i, s := range elems { - elemVals[i] = cty.StringVal(s) - } - if len(elemVals) == 0 { - return cty.ListValEmpty(cty.String), nil - } - return cty.ListVal(elemVals), nil - }, -}) - -// ChompFunc constructs a function that removes newline characters at the end of a string. -var ChompFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "str", - Type: cty.String, - }, - }, - Type: function.StaticReturnType(cty.String), - Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { - newlines := regexp.MustCompile(`(?:\r\n?|\n)*\z`) - return cty.StringVal(newlines.ReplaceAllString(args[0].AsString(), "")), nil - }, -}) - -// IndentFunc constructs a function that adds a given number of spaces to the -// beginnings of all but the first line in a given multi-line string. -var IndentFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "spaces", - Type: cty.Number, - }, - { - Name: "str", - Type: cty.String, - }, - }, - Type: function.StaticReturnType(cty.String), - Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { - var spaces int - if err := gocty.FromCtyValue(args[0], &spaces); err != nil { - return cty.UnknownVal(cty.String), err - } - data := args[1].AsString() - pad := strings.Repeat(" ", spaces) - return cty.StringVal(strings.Replace(data, "\n", "\n"+pad, -1)), nil - }, -}) - // ReplaceFunc constructs a function that searches a given string for another // given substring, and replaces each occurence with a given replacement string. var ReplaceFunc = function.New(&function.Spec{ @@ -201,158 +46,8 @@ var ReplaceFunc = function.New(&function.Spec{ }, }) -// TitleFunc constructs a function that converts the first letter of each word -// in the given string to uppercase. -var TitleFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "str", - Type: cty.String, - }, - }, - Type: function.StaticReturnType(cty.String), - Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { - return cty.StringVal(strings.Title(args[0].AsString())), nil - }, -}) - -// TrimSpaceFunc constructs a function that removes any space characters from -// the start and end of the given string. -var TrimSpaceFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "str", - Type: cty.String, - }, - }, - Type: function.StaticReturnType(cty.String), - Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { - return cty.StringVal(strings.TrimSpace(args[0].AsString())), nil - }, -}) - -// TrimFunc constructs a function that removes the specified characters from -// the start and end of the given string. -var TrimFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "str", - Type: cty.String, - }, - { - Name: "cutset", - Type: cty.String, - }, - }, - Type: function.StaticReturnType(cty.String), - Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { - str := args[0].AsString() - cutset := args[1].AsString() - return cty.StringVal(strings.Trim(str, cutset)), nil - }, -}) - -// TrimPrefixFunc constructs a function that removes the specified characters from -// the start the given string. -var TrimPrefixFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "str", - Type: cty.String, - }, - { - Name: "prefix", - Type: cty.String, - }, - }, - Type: function.StaticReturnType(cty.String), - Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { - str := args[0].AsString() - prefix := args[1].AsString() - return cty.StringVal(strings.TrimPrefix(str, prefix)), nil - }, -}) - -// TrimSuffixFunc constructs a function that removes the specified characters from -// the end of the given string. -var TrimSuffixFunc = function.New(&function.Spec{ - Params: []function.Parameter{ - { - Name: "str", - Type: cty.String, - }, - { - Name: "suffix", - Type: cty.String, - }, - }, - Type: function.StaticReturnType(cty.String), - Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { - str := args[0].AsString() - cutset := args[1].AsString() - return cty.StringVal(strings.TrimSuffix(str, cutset)), nil - }, -}) - -// Join concatenates together the string elements of one or more lists with a -// given separator. -func Join(sep cty.Value, lists ...cty.Value) (cty.Value, error) { - args := make([]cty.Value, len(lists)+1) - args[0] = sep - copy(args[1:], lists) - return JoinFunc.Call(args) -} - -// Sort re-orders the elements of a given list of strings so that they are -// in ascending lexicographical order. -func Sort(list cty.Value) (cty.Value, error) { - return SortFunc.Call([]cty.Value{list}) -} - -// Split divides a given string by a given separator, returning a list of -// strings containing the characters between the separator sequences. -func Split(sep, str cty.Value) (cty.Value, error) { - return SplitFunc.Call([]cty.Value{sep, str}) -} - -// Chomp removes newline characters at the end of a string. -func Chomp(str cty.Value) (cty.Value, error) { - return ChompFunc.Call([]cty.Value{str}) -} - -// Indent adds a given number of spaces to the beginnings of all but the first -// line in a given multi-line string. -func Indent(spaces, str cty.Value) (cty.Value, error) { - return IndentFunc.Call([]cty.Value{spaces, str}) -} - // Replace searches a given string for another given substring, // and replaces all occurences with a given replacement string. func Replace(str, substr, replace cty.Value) (cty.Value, error) { return ReplaceFunc.Call([]cty.Value{str, substr, replace}) } - -// Title converts the first letter of each word in the given string to uppercase. -func Title(str cty.Value) (cty.Value, error) { - return TitleFunc.Call([]cty.Value{str}) -} - -// TrimSpace removes any space characters from the start and end of the given string. -func TrimSpace(str cty.Value) (cty.Value, error) { - return TrimSpaceFunc.Call([]cty.Value{str}) -} - -// Trim removes the specified characters from the start and end of the given string. -func Trim(str, cutset cty.Value) (cty.Value, error) { - return TrimFunc.Call([]cty.Value{str, cutset}) -} - -// TrimPrefix removes the specified prefix from the start of the given string. -func TrimPrefix(str, prefix cty.Value) (cty.Value, error) { - return TrimPrefixFunc.Call([]cty.Value{str, prefix}) -} - -// TrimSuffix removes the specified suffix from the end of the given string. -func TrimSuffix(str, suffix cty.Value) (cty.Value, error) { - return TrimSuffixFunc.Call([]cty.Value{str, suffix}) -} diff --git a/lang/funcs/string_test.go b/lang/funcs/string_test.go index 19e266876..7b44a2762 100644 --- a/lang/funcs/string_test.go +++ b/lang/funcs/string_test.go @@ -7,360 +7,6 @@ import ( "github.com/zclconf/go-cty/cty" ) -func TestJoin(t *testing.T) { - tests := []struct { - Sep cty.Value - Lists []cty.Value - Want cty.Value - }{ - { - cty.StringVal(" "), - []cty.Value{ - cty.ListVal([]cty.Value{ - cty.StringVal("Hello"), - cty.StringVal("World"), - }), - }, - cty.StringVal("Hello World"), - }, - { - cty.StringVal(" "), - []cty.Value{ - cty.ListVal([]cty.Value{ - cty.StringVal("Hello"), - cty.StringVal("World"), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("Foo"), - cty.StringVal("Bar"), - }), - }, - cty.StringVal("Hello World Foo Bar"), - }, - { - cty.StringVal(" "), - []cty.Value{ - cty.ListValEmpty(cty.String), - }, - cty.StringVal(""), - }, - { - cty.StringVal(" "), - []cty.Value{ - cty.ListValEmpty(cty.String), - cty.ListValEmpty(cty.String), - cty.ListValEmpty(cty.String), - }, - cty.StringVal(""), - }, - { - cty.StringVal(" "), - []cty.Value{ - cty.ListValEmpty(cty.String), - cty.ListVal([]cty.Value{ - cty.StringVal("Foo"), - cty.StringVal("Bar"), - }), - }, - cty.StringVal("Foo Bar"), - }, - { - cty.UnknownVal(cty.String), - []cty.Value{ - cty.ListVal([]cty.Value{ - cty.StringVal("Hello"), - cty.StringVal("World"), - }), - }, - cty.UnknownVal(cty.String), - }, - { - cty.StringVal(" "), - []cty.Value{ - cty.ListVal([]cty.Value{ - cty.StringVal("Hello"), - cty.UnknownVal(cty.String), - }), - }, - cty.UnknownVal(cty.String), - }, - { - cty.StringVal(" "), - []cty.Value{ - cty.UnknownVal(cty.List(cty.String)), - }, - cty.UnknownVal(cty.String), - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("Join(%#v, %#v...)", test.Sep, test.Lists), func(t *testing.T) { - got, err := Join(test.Sep, test.Lists...) - - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - -func TestSort(t *testing.T) { - tests := []struct { - List cty.Value - Want cty.Value - }{ - { - cty.ListValEmpty(cty.String), - cty.ListValEmpty(cty.String), - }, - { - cty.ListVal([]cty.Value{ - cty.StringVal("banana"), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("banana"), - }), - }, - { - cty.ListVal([]cty.Value{ - cty.StringVal("banana"), - cty.StringVal("apple"), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("apple"), - cty.StringVal("banana"), - }), - }, - { - cty.ListVal([]cty.Value{ - cty.StringVal("8"), - cty.StringVal("9"), - cty.StringVal("10"), - }), - cty.ListVal([]cty.Value{ - cty.StringVal("10"), // lexicographical sort, not numeric sort - cty.StringVal("8"), - cty.StringVal("9"), - }), - }, - { - cty.UnknownVal(cty.List(cty.String)), - cty.UnknownVal(cty.List(cty.String)), - }, - { - cty.ListVal([]cty.Value{ - cty.UnknownVal(cty.String), - }), - cty.UnknownVal(cty.List(cty.String)), - }, - { - cty.ListVal([]cty.Value{ - cty.UnknownVal(cty.String), - cty.StringVal("banana"), - }), - cty.UnknownVal(cty.List(cty.String)), - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("Sort(%#v)", test.List), func(t *testing.T) { - got, err := Sort(test.List) - - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} -func TestSplit(t *testing.T) { - tests := []struct { - Sep cty.Value - Str cty.Value - Want cty.Value - }{ - { - cty.StringVal(" "), - cty.StringVal("Hello World"), - cty.ListVal([]cty.Value{ - cty.StringVal("Hello"), - cty.StringVal("World"), - }), - }, - { - cty.StringVal(" "), - cty.StringVal("Hello"), - cty.ListVal([]cty.Value{ - cty.StringVal("Hello"), - }), - }, - { - cty.StringVal(" "), - cty.StringVal(""), - cty.ListVal([]cty.Value{ - cty.StringVal(""), - }), - }, - { - cty.StringVal(""), - cty.StringVal(""), - cty.ListValEmpty(cty.String), - }, - { - cty.UnknownVal(cty.String), - cty.StringVal("Hello World"), - cty.UnknownVal(cty.List(cty.String)), - }, - { - cty.StringVal(" "), - cty.UnknownVal(cty.String), - cty.UnknownVal(cty.List(cty.String)), - }, - { - cty.UnknownVal(cty.String), - cty.UnknownVal(cty.String), - cty.UnknownVal(cty.List(cty.String)), - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("Split(%#v, %#v)", test.Sep, test.Str), func(t *testing.T) { - got, err := Split(test.Sep, test.Str) - - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - -func TestChomp(t *testing.T) { - tests := []struct { - String cty.Value - Want cty.Value - Err bool - }{ - { - cty.StringVal("hello world"), - cty.StringVal("hello world"), - false, - }, - { - cty.StringVal("goodbye\ncruel\nworld"), - cty.StringVal("goodbye\ncruel\nworld"), - false, - }, - { - cty.StringVal("goodbye\r\nwindows\r\nworld"), - cty.StringVal("goodbye\r\nwindows\r\nworld"), - false, - }, - { - cty.StringVal("goodbye\ncruel\nworld\n"), - cty.StringVal("goodbye\ncruel\nworld"), - false, - }, - { - cty.StringVal("goodbye\ncruel\nworld\n\n\n\n"), - cty.StringVal("goodbye\ncruel\nworld"), - false, - }, - { - cty.StringVal("goodbye\r\nwindows\r\nworld\r\n"), - cty.StringVal("goodbye\r\nwindows\r\nworld"), - false, - }, - { - cty.StringVal("goodbye\r\nwindows\r\nworld\r\n\r\n\r\n\r\n"), - cty.StringVal("goodbye\r\nwindows\r\nworld"), - false, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("chomp(%#v)", test.String), func(t *testing.T) { - got, err := Chomp(test.String) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - -func TestIndent(t *testing.T) { - tests := []struct { - String cty.Value - Spaces cty.Value - Want cty.Value - Err bool - }{ - { - cty.StringVal(`Fleas: -Adam -Had'em - -E.E. Cummings`), - cty.NumberIntVal(4), - cty.StringVal("Fleas:\n Adam\n Had'em\n \n E.E. Cummings"), - false, - }, - { - cty.StringVal("oneliner"), - cty.NumberIntVal(4), - cty.StringVal("oneliner"), - false, - }, - { - cty.StringVal(`#!/usr/bin/env bash -date -pwd`), - cty.NumberIntVal(4), - cty.StringVal("#!/usr/bin/env bash\n date\n pwd"), - false, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("indent(%#v, %#v)", test.Spaces, test.String), func(t *testing.T) { - got, err := Indent(test.Spaces, test.String) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - func TestReplace(t *testing.T) { tests := []struct { String cty.Value @@ -425,207 +71,3 @@ func TestReplace(t *testing.T) { }) } } - -func TestTitle(t *testing.T) { - tests := []struct { - String cty.Value - Want cty.Value - Err bool - }{ - { - cty.StringVal("hello"), - cty.StringVal("Hello"), - false, - }, - { - cty.StringVal("hello world"), - cty.StringVal("Hello World"), - false, - }, - { - cty.StringVal(""), - cty.StringVal(""), - false, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("title(%#v)", test.String), func(t *testing.T) { - got, err := Title(test.String) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - -func TestTrimSpace(t *testing.T) { - tests := []struct { - String cty.Value - Want cty.Value - Err bool - }{ - { - cty.StringVal(" hello "), - cty.StringVal("hello"), - false, - }, - { - cty.StringVal(""), - cty.StringVal(""), - false, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("trimspace(%#v)", test.String), func(t *testing.T) { - got, err := TrimSpace(test.String) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - -func TestTrim(t *testing.T) { - tests := []struct { - String cty.Value - Cutset cty.Value - Want cty.Value - Err bool - }{ - { - cty.StringVal("!? test ?!"), - cty.StringVal("?!"), - cty.StringVal(" test "), - false, - }, - { - cty.StringVal("...test..."), - cty.StringVal("."), - cty.StringVal("test"), - false, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("trim(%#v, %#v)", test.String, test.Cutset), func(t *testing.T) { - got, err := Trim(test.String, test.Cutset) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - -func TestTrimPrefix(t *testing.T) { - tests := []struct { - String cty.Value - Prefix cty.Value - Want cty.Value - Err bool - }{ - { - cty.StringVal("helloworld"), - cty.StringVal("hello"), - cty.StringVal("world"), - false, - }, - { - cty.StringVal("...test..."), - cty.StringVal("."), - cty.StringVal("..test..."), - false, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("trimprefix(%#v, %#v)", test.String, test.Prefix), func(t *testing.T) { - got, err := TrimPrefix(test.String, test.Prefix) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -} - -func TestTrimSuffix(t *testing.T) { - tests := []struct { - String cty.Value - Suffix cty.Value - Want cty.Value - Err bool - }{ - { - cty.StringVal("helloworld"), - cty.StringVal("world"), - cty.StringVal("hello"), - false, - }, - { - cty.StringVal("...test..."), - cty.StringVal("."), - cty.StringVal("...test.."), - false, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("trimright(%#v, %#v)", test.String, test.Suffix), func(t *testing.T) { - got, err := TrimSuffix(test.String, test.Suffix) - - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") - } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) - } - }) - } -}