lang/funcs: "anytrue" function

This is an analog to the "alltrue" function, using OR as the reduce
operator rather than AND.

This also includes some simplification of the "alltrue" implementation
to implement it similarly as a sort of reduce operation with AND
as the reduce operator, but with the same effective behavior.
This commit is contained in:
Arthur Burkart 2020-10-23 16:52:48 -04:00 committed by GitHub
parent 5522a799f5
commit d4716a69e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 158 additions and 59 deletions

View File

@ -57,40 +57,54 @@ var LengthFunc = function.New(&function.Spec{
})
// AllTrueFunc constructs a function that returns true if all elements of the
// collection are true or "true". If the collection is empty, return true.
// list are true. If the list is empty, return true.
var AllTrueFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "collection",
Type: cty.DynamicPseudoType,
Name: "list",
Type: cty.List(cty.Bool),
},
},
Type: function.StaticReturnType(cty.Bool),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
ty := args[0].Type()
if !ty.IsListType() && !ty.IsTupleType() && !ty.IsSetType() {
return cty.NilVal, errors.New("argument must be list, tuple, or set")
}
tobool := MakeToFunc(cty.Bool)
result := cty.True
for it := args[0].ElementIterator(); it.Next(); {
_, v := it.Element()
if !v.IsKnown() {
return cty.UnknownVal(cty.Bool), nil
}
got, err := tobool.Call([]cty.Value{v})
if err != nil {
if v.IsNull() {
return cty.False, nil
}
eq, err := stdlib.Equal(got, cty.True)
if err != nil {
return cty.NilVal, err
}
if eq.False() {
result = result.And(v)
if result.False() {
return cty.False, nil
}
}
return cty.True, nil
return result, nil
},
})
// AnyTrueFunc constructs a function that returns true if any element of the
// list is true. If the list is empty, return false.
var AnyTrueFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "list",
Type: cty.List(cty.Bool),
},
},
Type: function.StaticReturnType(cty.Bool),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
result := cty.False
for it := args[0].ElementIterator(); it.Next(); {
_, v := it.Element()
if v.IsNull() {
continue
}
result = result.Or(v)
if result.True() {
return cty.True, nil
}
}
return result, nil
},
})
@ -620,12 +634,18 @@ func Length(collection cty.Value) (cty.Value, error) {
return LengthFunc.Call([]cty.Value{collection})
}
// AllTrue returns true if all elements of the collection are true or "true".
// If the collection is empty, return true.
// AllTrue returns true if all elements of the list are true. If the list is empty,
// return true.
func AllTrue(collection cty.Value) (cty.Value, error) {
return AllTrueFunc.Call([]cty.Value{collection})
}
// AnyTrue returns true if any element of the list is true. If the list is empty,
// return false.
func AnyTrue(collection cty.Value) (cty.Value, error) {
return AnyTrueFunc.Call([]cty.Value{collection})
}
// Coalesce takes any number of arguments and returns the first one that isn't empty.
func Coalesce(args ...cty.Value) (cty.Value, error) {
return CoalesceFunc.Call(args)

View File

@ -146,17 +146,7 @@ func TestAllTrue(t *testing.T) {
Err bool
}{
{
cty.ListValEmpty(cty.String),
cty.True,
false,
},
{
cty.TupleVal([]cty.Value{}),
cty.True,
false,
},
{
cty.SetValEmpty(cty.Bool),
cty.ListValEmpty(cty.Bool),
cty.True,
false,
},
@ -165,16 +155,6 @@ func TestAllTrue(t *testing.T) {
cty.True,
false,
},
{
cty.ListVal([]cty.Value{cty.StringVal("true")}),
cty.True,
false,
},
{
cty.TupleVal([]cty.Value{cty.True, cty.StringVal("true")}),
cty.True,
false,
},
{
cty.ListVal([]cty.Value{cty.False}),
cty.False,
@ -191,24 +171,14 @@ func TestAllTrue(t *testing.T) {
false,
},
{
cty.ListVal([]cty.Value{cty.NumberIntVal(1)}),
cty.False,
false,
},
{
cty.StringVal("true"),
cty.False,
cty.ListVal([]cty.Value{cty.UnknownVal(cty.Bool)}),
cty.UnknownVal(cty.Bool),
true,
},
{
cty.ListVal([]cty.Value{cty.ListValEmpty(cty.String)}),
cty.False,
false,
},
{
cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)}),
cty.UnknownVal(cty.Bool),
false,
cty.NullVal(cty.List(cty.Bool)),
cty.NilVal,
true,
},
}
@ -232,6 +202,69 @@ func TestAllTrue(t *testing.T) {
}
}
func TestAnyTrue(t *testing.T) {
tests := []struct {
Collection cty.Value
Want cty.Value
Err bool
}{
{
cty.ListValEmpty(cty.Bool),
cty.False,
false,
},
{
cty.ListVal([]cty.Value{cty.True}),
cty.True,
false,
},
{
cty.ListVal([]cty.Value{cty.False}),
cty.False,
false,
},
{
cty.ListVal([]cty.Value{cty.True, cty.False}),
cty.True,
false,
},
{
cty.ListVal([]cty.Value{cty.False, cty.True}),
cty.True,
false,
},
{
cty.ListVal([]cty.Value{cty.UnknownVal(cty.Bool)}),
cty.UnknownVal(cty.Bool),
true,
},
{
cty.NullVal(cty.List(cty.Bool)),
cty.NilVal,
true,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("anytrue(%#v)", test.Collection), func(t *testing.T) {
got, err := AnyTrue(test.Collection)
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 TestCoalesce(t *testing.T) {
tests := []struct {
Values []cty.Value

View File

@ -34,6 +34,7 @@ func (s *Scope) Functions() map[string]function.Function {
"abs": stdlib.AbsoluteFunc,
"abspath": funcs.AbsPathFunc,
"alltrue": funcs.AllTrueFunc,
"anytrue": funcs.AnyTrueFunc,
"basename": funcs.BasenameFunc,
"base64decode": funcs.Base64DecodeFunc,
"base64encode": funcs.Base64EncodeFunc,

View File

@ -69,11 +69,18 @@ func TestFunctions(t *testing.T) {
"alltrue": {
{
`alltrue([true])`,
`alltrue(["true", true])`,
cty.True,
},
},
"anytrue": {
{
`anytrue([])`,
cty.False,
},
},
"base64decode": {
{
`base64decode("YWJjMTIzIT8kKiYoKSctPUB+")`,

View File

@ -0,0 +1,34 @@
---
layout: functions
page_title: anytrue - Functions - Configuration Language
sidebar_current: docs-funcs-collection-anytrue
description: |-
The anytrue function determines whether any element of a collection
is true or "true". If the collection is empty, it returns false.
---
# `anytrue` Function
-> **Note:** This page is about Terraform 0.12 and later. For Terraform 0.11 and
earlier, see
[0.11 Configuration Language: Interpolation Syntax](../../configuration-0-11/interpolation.html).
`anytrue` returns `true` if any element in a given collection is `true`
or `"true"`. It also returns `false` if the collection is empty.
```hcl
anytrue(list)
```
## Examples
```command
> anytrue(["true"])
true
> anytrue([true])
true
> anytrue([true, false])
true
> anytrue([])
false
```

View File

@ -142,6 +142,10 @@
<a href="/docs/configuration/functions/alltrue.html">alltrue</a>
</li>
<li>
<a href="/docs/configuration/functions/anytrue.html">anytrue</a>
</li>
<li>
<a href="/docs/configuration/functions/chunklist.html">chunklist</a>
</li>