diff --git a/functions.go b/functions.go index e9770e8..4dad64a 100644 --- a/functions.go +++ b/functions.go @@ -342,6 +342,17 @@ func (e *functionEntry) resolveArgs(arguments []interface{}) ([]interface{}, err if len(arguments) < len(e.arguments) { return nil, errors.New("invalid arity") } + for i, userArg := range arguments { + var spec argSpec + if i < len(e.arguments)-1 { + spec = e.arguments[i] + } else { + spec = e.arguments[len(e.arguments)-1] + } + if err := spec.typeCheck(userArg); err != nil { + return nil, err + } + } return arguments, nil } diff --git a/interpreter_test.go b/interpreter_test.go index 76e0c5d..e0e3bf2 100644 --- a/interpreter_test.go +++ b/interpreter_test.go @@ -181,6 +181,40 @@ func TestCanSupportSliceOfStructsWithFunctions(t *testing.T) { assert.Equal(result.(float64), 2.0) } +func TestVariadicFunctionTypeChecking(t *testing.T) { + assert := assert.New(t) + + // Issue #72: merge should return error instead of panicking when an argument is not an object. + data := map[string]interface{}{"message": "foo"} + result, err := Search("merge({message: message}, contextMap)", data) + assert.Nil(result) + assert.NotNil(err) + + invalidExpressions := []string{ + "merge(`\"foo\"`)", + "merge(`123`)", + "merge(`[1, 2]`)", + "merge(`true`)", + "merge(`{\"a\": 1}`, `\"b\"`)", + "merge(`{\"a\": 1}`, `null`)", + } + for _, expr := range invalidExpressions { + res, err := Search(expr, nil) + assert.Nil(res) + assert.NotNil(err) + } + + // merge with valid objects should still succeed + res, err := Search("merge(`{\"a\": 1}`, `{\"b\": 2}`)", nil) + assert.Nil(err) + assert.Equal(map[string]interface{}{"a": float64(1), "b": float64(2)}, res) + + // not_null with variadic arguments should still succeed + res, err = Search("not_null(foo, bar, `\"default\"`)", map[string]interface{}{}) + assert.Nil(err) + assert.Equal("default", res) +} + func BenchmarkInterpretSingleFieldStruct(b *testing.B) { assert := assert.New(b) intr := newInterpreter()