From fca3b0df26ef0e724a7bacedb0e1721d01c2d1ab Mon Sep 17 00:00:00 2001 From: James Bardin Date: Mon, 24 Feb 2020 09:16:49 -0500 Subject: [PATCH] fixed Contains stdlib function Contains was transferred from Terraform, but the Index function which it was calling has the inverse meaning than the Terraform function. --- cty/function/stdlib/collection.go | 35 ++++++- cty/function/stdlib/collection_test.go | 132 +++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 4 deletions(-) diff --git a/cty/function/stdlib/collection.go b/cty/function/stdlib/collection.go index aae7bfb7..b2ce062a 100644 --- a/cty/function/stdlib/collection.go +++ b/cty/function/stdlib/collection.go @@ -299,7 +299,7 @@ var ContainsFunc = function.New(&function.Spec{ }, }, Type: function.StaticReturnType(cty.Bool), - Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { arg := args[0] ty := arg.Type() @@ -307,12 +307,39 @@ var ContainsFunc = function.New(&function.Spec{ return cty.NilVal, errors.New("argument must be list, tuple, or set") } - _, err = Index(cty.TupleVal(arg.AsValueSlice()), args[1]) - if err != nil { + if args[0].IsNull() { + return cty.NilVal, errors.New("cannot search a nil list or set") + } + + if args[0].LengthInt() == 0 { return cty.False, nil } - return cty.True, nil + if !args[0].IsKnown() || !args[1].IsKnown() { + return cty.UnknownVal(cty.Bool), nil + } + + containsUnknown := false + for it := args[0].ElementIterator(); it.Next(); { + _, v := it.Element() + eq := args[1].Equals(v) + if !eq.IsKnown() { + // We may have an unknown value which could match later, but we + // first need to continue checking all values for an exact + // match. + containsUnknown = true + continue + } + if eq.True() { + return cty.True, nil + } + } + + if containsUnknown { + return cty.UnknownVal(cty.Bool), nil + } + + return cty.False, nil }, }) diff --git a/cty/function/stdlib/collection_test.go b/cty/function/stdlib/collection_test.go index 01fb3cdd..d1b7dc2a 100644 --- a/cty/function/stdlib/collection_test.go +++ b/cty/function/stdlib/collection_test.go @@ -90,6 +90,138 @@ func TestHasIndex(t *testing.T) { } } +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, + }, + { + listWithUnknown, + cty.StringVal("orange"), + cty.UnknownVal(cty.Bool), + 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, + }, + { // nested unknown + cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "a": cty.UnknownVal(cty.String), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "a": cty.StringVal("b"), + }), + cty.UnknownVal(cty.Bool), + 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 TestMerge(t *testing.T) { tests := []struct { Values []cty.Value