Skip to content

Commit

Permalink
function/stdlib: fix "Contains" function
Browse files Browse the repository at this point in the history
Contains was transferred from Terraform, but the Index function which it
was calling has the inverse meaning than the Terraform function.
  • Loading branch information
jbardin authored Mar 3, 2020
1 parent a0df1d8 commit 3a0df52
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 4 deletions.
35 changes: 31 additions & 4 deletions cty/function/stdlib/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,20 +299,47 @@ 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()

if !ty.IsListType() && !ty.IsTupleType() && !ty.IsSetType() {
return cty.NilVal, errors.New("argument must be list, tuple, or set")
}

_, err = Index(cty.TupleVal(arg.AsValueSlice()), args[1])
if err != nil {
if 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
},
})

Expand Down
132 changes: 132 additions & 0 deletions cty/function/stdlib/collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 3a0df52

Please sign in to comment.