Repository: ariga/atlas
Pull request #3358 (2025-02-02 07:26:41)

schemahcl: add empty and alltrue to stdlib
Approved internally. 


Patch:
diff --git a/schemahcl/stdlib.go b/schemahcl/stdlib.go
index 4f134ec5..8f84eb56 100644
--- a/schemahcl/stdlib.go
+++ b/schemahcl/stdlib.go
@@ -109,6 +109,7 @@ func stdTypes(ctx *hcl.EvalContext) *hcl.EvalContext {
 func stdFuncs() map[string]function.Function {
 	return map[string]function.Function{
 		"abs":             stdlib.AbsoluteFunc,
+		"alltrue":         allTrueFunc,
 		"can":             tryfunc.CanFunc,
 		"ceil":            stdlib.CeilFunc,
 		"chomp":           stdlib.ChompFunc,
@@ -120,6 +121,7 @@ func stdFuncs() map[string]function.Function {
 		"csvdecode":       stdlib.CSVDecodeFunc,
 		"distinct":        stdlib.DistinctFunc,
 		"element":         stdlib.ElementFunc,
+		"empty":           emptyFunc,
 		"endswith":        endsWithFunc,
 		"flatten":         stdlib.FlattenFunc,
 		"floor":           stdlib.FloorFunc,
@@ -463,6 +465,63 @@ var (
 			return cty.StringVal(regexp.QuoteMeta(args[0].AsString())), nil
 		},
 	})
+
+	refineNonNull = func(b *cty.RefinementBuilder) *cty.RefinementBuilder {
+		return b.NotNull()
+	}
+
+	emptyFunc = function.New(&function.Spec{
+		Description: `Returns a boolean indicating whether the given collection is empty.`,
+		Params: []function.Parameter{
+			{
+				Name:             "collection",
+				Type:             cty.DynamicPseudoType,
+				AllowDynamicType: true,
+				AllowUnknown:     true,
+				AllowMarked:      true,
+			},
+		},
+		Type: func(args []cty.Value) (ret cty.Type, err error) {
+			collTy := args[0].Type()
+			if !(collTy.IsTupleType() || collTy.IsListType() || collTy.IsMapType() || collTy.IsSetType() || collTy == cty.DynamicPseudoType) {
+				return cty.NilType, fmt.Errorf("collection must be a list, a map or a tuple")
+			}
+			return cty.Bool, nil
+		},
+		RefineResult: refineNonNull,
+		Impl: func(args []cty.Value, _ cty.Type) (ret cty.Value, err error) {
+			return args[0].Length().Equals(cty.NumberIntVal(0)), nil
+		},
+	})
+
+	allTrueFunc = function.New(&function.Spec{
+		Description: `Returns a boolean indicating whether all elements in the given collection are true.`,
+		Params: []function.Parameter{
+			{
+				Name:             "collection",
+				Type:             cty.DynamicPseudoType,
+				AllowDynamicType: true,
+				AllowUnknown:     true,
+				AllowMarked:      true,
+			},
+		},
+		Type: func(args []cty.Value) (ret cty.Type, err error) {
+			collTy := args[0].Type()
+			if !(collTy.IsTupleType() || collTy.IsListType() || collTy.IsMapType() || collTy.IsSetType() || collTy == cty.DynamicPseudoType) {
+				return cty.NilType, fmt.Errorf("collection must be a list, a map or a tuple")
+			}
+			return cty.Bool, nil
+		},
+		RefineResult: refineNonNull,
+		Impl: func(args []cty.Value, _ cty.Type) (ret cty.Value, err error) {
+			for _, v := range args[0].AsValueSlice() {
+				if v.Type() != cty.Bool || !v.True() {
+					return cty.False, nil
+				}
+			}
+			return cty.True, nil
+		},
+	})
 )
 
 // MakeFileFunc returns a function that reads a file


Test patch:
diff --git a/schemahcl/stdlib_test.go b/schemahcl/stdlib_test.go
index 47a2ab62..144e5daf 100644
--- a/schemahcl/stdlib_test.go
+++ b/schemahcl/stdlib_test.go
@@ -156,6 +156,27 @@ func TestEndsWithFunc(t *testing.T) {
 	require.Equal(t, cty.True, got)
 }
 
+func TestEmptyFunc(t *testing.T) {
+	got, err := emptyFunc.Call([]cty.Value{cty.ListValEmpty(cty.String)})
+	require.NoError(t, err)
+	require.Equal(t, cty.True, got)
+	got, err = emptyFunc.Call([]cty.Value{cty.SetValEmpty(cty.String)})
+	require.NoError(t, err)
+	require.Equal(t, cty.True, got)
+	got, err = emptyFunc.Call([]cty.Value{cty.MapValEmpty(cty.String)})
+	require.NoError(t, err)
+	require.Equal(t, cty.True, got)
+	got, err = emptyFunc.Call([]cty.Value{cty.EmptyTupleVal})
+	require.NoError(t, err)
+	require.Equal(t, cty.True, got)
+	got, err = emptyFunc.Call([]cty.Value{cty.ListVal([]cty.Value{cty.StringVal("a")})})
+	require.NoError(t, err)
+	require.Equal(t, cty.False, got)
+	// Invalid value.
+	got, err = emptyFunc.Call([]cty.Value{cty.StringVal("a")})
+	require.EqualError(t, err, "collection must be a list, a map or a tuple")
+}
+
 func TestRegexpEscapeFunc(t *testing.T) {
 	got, err := regexpEscape.Call([]cty.Value{cty.StringVal("a|b|c")})
 	require.NoError(t, err)