Skip to content

Commit

Permalink
adds functions for checking the type of variables
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel.schroeder committed Oct 26, 2019
1 parent 8a34c33 commit 9eaa310
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 0 deletions.
69 changes: 69 additions & 0 deletions lang/funcs/type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package funcs

import (
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
)

var TypeFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "variable",
Type: cty.DynamicPseudoType,
AllowNull: true,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
varType := getType(args[0])
return cty.StringVal(varType), nil
},
})

// MakeIsTypeFunc constructs a "is..." function, like "isstring", which checks
// the type of a given variable is of the expected type.
// The given string checkType can be any type supported by the cty package
func MakeIsTypeFunc(checkType string) function.Function {
return function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "variable",
Type: cty.DynamicPseudoType,
AllowNull: true,
},
},
Type: function.StaticReturnType(cty.Bool),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
checked := getType(args[0]) == checkType
return cty.BoolVal(checked), nil
},
})
}

func getType(arg cty.Value) string {
var varType string

// We have some special cases here, because var.Type().FriendlyName() does
// not always give what we want.
// In case the input var is null, it returns "any value"
// For collection types it returns "list/map/set of strings" etc
// We only want "list/map/set" instead.
if arg.IsNull() {
varType = "null"
} else if arg.Type().IsListType() {
varType = "list"
} else if arg.Type().IsMapType() {
varType = "map"
} else if arg.Type().IsSetType() {
varType = "set"
} else {
varType = arg.Type().FriendlyName()
}

return varType
}

// Type returns the type of a given var as string
func Type(t cty.Value) (cty.Value, error) {
return TypeFunc.Call([]cty.Value{t})
}
113 changes: 113 additions & 0 deletions lang/funcs/type_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package funcs

import (
"fmt"
"testing"

"github.com/zclconf/go-cty/cty"
)

func TestType(t *testing.T) {
tests := []struct {
Value cty.Value
Want string
}{
{
cty.BoolVal(true),
"bool",
},
{
cty.ListVal([]cty.Value{
cty.StringVal("foo"),
cty.StringVal("bar"),
}),
"list",
},
{
cty.MapVal(map[string]cty.Value{
"foo": cty.StringVal("foo"),
"bar": cty.StringVal("bar")}),
"map",
},
{
cty.NilVal,
"null",
},
{
cty.NumberIntVal(42),
"number",
},
{
cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("foo"),
"bar": cty.StringVal("bar")}),
"object",
},
{
cty.SetVal([]cty.Value{
cty.StringVal("foo"),
cty.StringVal("bar"),
}),
"set",
},
{
cty.StringVal("foo"),
"string",
},
{
cty.TupleVal([]cty.Value{
cty.StringVal("foo"),
cty.StringVal("bar"),
}),
"tuple",
},
}

// prevValue is used to test negative results, to ensure the is* functions
// do not just always return true
prevValue := cty.NumberIntVal(42)

for _, test := range tests {

t.Run(fmt.Sprintf("type(%#v)", test.Value), func(t *testing.T) {
got, err := Type(test.Value)

if err != nil {
t.Fatalf("unexpected error: %s", err)
}

if got.AsString() != test.Want {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})

t.Run(fmt.Sprintf("is%s()", test.Want), func(t *testing.T) {
f := MakeIsTypeFunc(test.Want)

//testing for positive results
got, err := f.Call([]cty.Value{test.Value})

if err != nil {
t.Fatalf("unexpected error: %s", err)
}

if got.False() {
t.Error("wrong result\ngot: false\nwant: true")
}

// testing for negative results
got, err = f.Call([]cty.Value{prevValue})

if err != nil {
t.Fatalf("unexpected error: %s", err)
}

if got.True() {
t.Error("wrong result\ngot: true\nwant: false")
}

// preparing prevValue for next test iteration
prevValue = test.Value
})
}
}
10 changes: 10 additions & 0 deletions lang/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ func (s *Scope) Functions() map[string]function.Function {
"formatlist": stdlib.FormatListFunc,
"indent": funcs.IndentFunc,
"index": funcs.IndexFunc,
"isbool": funcs.MakeIsTypeFunc("bool"),
"islist": funcs.MakeIsTypeFunc("list"),
"ismap": funcs.MakeIsTypeFunc("map"),
"isnull": funcs.MakeIsTypeFunc("null"),
"isnumber": funcs.MakeIsTypeFunc("number"),
"isobject": funcs.MakeIsTypeFunc("object"),
"isset": funcs.MakeIsTypeFunc("set"),
"isstring": funcs.MakeIsTypeFunc("string"),
"istuple": funcs.MakeIsTypeFunc("tuple"),
"join": funcs.JoinFunc,
"jsondecode": stdlib.JSONDecodeFunc,
"jsonencode": stdlib.JSONEncodeFunc,
Expand Down Expand Up @@ -119,6 +128,7 @@ func (s *Scope) Functions() map[string]function.Function {
"tomap": funcs.MakeToFunc(cty.Map(cty.DynamicPseudoType)),
"transpose": funcs.TransposeFunc,
"trimspace": funcs.TrimSpaceFunc,
"type": funcs.TypeFunc,
"upper": stdlib.UpperFunc,
"urlencode": funcs.URLEncodeFunc,
"uuid": funcs.UUIDFunc,
Expand Down

0 comments on commit 9eaa310

Please sign in to comment.