diff --git a/plugin/shared/call.go b/plugin/shared/call.go index 49cafad4..8d30516d 100644 --- a/plugin/shared/call.go +++ b/plugin/shared/call.go @@ -8,91 +8,25 @@ import ( ) // CallFunc calls function with arguments -// it is used when calling go plugin or builtin functions func CallFunc(fn reflect.Value, args ...interface{}) (interface{}, error) { - fnArgsNum := fn.Type().NumIn() - - // function expect 0 argument - if fnArgsNum == 0 { - if len(args) > 0 { - return nil, fmt.Errorf("function expect 0 argument, but got %d", len(args)) - } - return call(fn, nil) - } - - // fnArgsNum > 0: function expect more than 0 arguments - // function last argument is not slice - if fn.Type().In(fnArgsNum-1).Kind() != reflect.Slice { - // function arguments should match exactly - if fnArgsNum != len(args) { - return nil, fmt.Errorf("function expect %d arguments, but got %d", fnArgsNum, len(args)) - } - // prepare arguments - argumentsValue, err := convertFirstNArgs(fn, fnArgsNum, args...) - if err != nil { - return nil, err - } - return call(fn, argumentsValue) - } - - // function last argument is slice - // e.g. - // ...interface{} - // a, b string, c ...interface{} - // []int - // a, b string, c []int - - // arguments number should >= fnArgsNum-1 - if len(args) < fnArgsNum-1 { - return nil, fmt.Errorf("function expect at least %d arguments, but got %d", fnArgsNum-1, len(args)) - } - - // prepare first n-1 arguments - argumentsValue, err := convertFirstNArgs(fn, fnArgsNum-1, args...) + argumentsValue, err := convertArgs(fn, args...) if err != nil { + log.Error().Err(err).Msg("convert arguments failed") return nil, err } - - // prepare last slice argument - fnLastSliceElemType := fn.Type().In(fnArgsNum - 1).Elem() // slice element type - for i := fnArgsNum - 1; i < len(args); i++ { // loop last len(args)-fnArgsNum+1 arguments - argValue := reflect.ValueOf(args[i]) - argValueType := reflect.TypeOf(args[i]) - - // check if slice element type match - if fnLastSliceElemType == argValueType { - argumentsValue = append(argumentsValue, argValue) - continue - } - - // last argument is also slice, e.g. []int - if argValueType.Kind() == reflect.Slice { - if argValueType.Elem() != fnLastSliceElemType { - err := fmt.Errorf("function argument %d's slice element type is not match, expect %v, actual %v", - i, fnLastSliceElemType, argValueType) - return nil, err - } - argumentsValue = append(argumentsValue, argValue) - continue - } - - // type not match, check if convertible - if !argValueType.ConvertibleTo(fnLastSliceElemType) { - // function argument type not match and not convertible - err := fmt.Errorf("function argument %d's type is neither match nor convertible, expect %v, actual %v", - i, fnLastSliceElemType, argValueType) - return nil, err - } - // convert argument to expect type - argumentsValue = append(argumentsValue, argValue.Convert(fnLastSliceElemType)) - } - return call(fn, argumentsValue) } -func convertFirstNArgs(fn reflect.Value, n int, args ...interface{}) ([]reflect.Value, error) { - argumentsValue := make([]reflect.Value, n) - for index := 0; index < n; index++ { +func convertArgs(fn reflect.Value, args ...interface{}) ([]reflect.Value, error) { + fnArgsNum := fn.Type().NumIn() + + // function arguments should match exactly if function's last argument is not slice + if len(args) != fnArgsNum && (fnArgsNum == 0 || fn.Type().In(fnArgsNum-1).Kind() != reflect.Slice) { + return nil, fmt.Errorf("function expect %d arguments, but got %d", fnArgsNum, len(args)) + } + + argumentsValue := make([]reflect.Value, len(args)) + for index := 0; index < len(args); index++ { argument := args[index] if argument == nil { argumentsValue[index] = reflect.Zero(fn.Type().In(index)) @@ -100,9 +34,27 @@ func convertFirstNArgs(fn reflect.Value, n int, args ...interface{}) ([]reflect. } argumentValue := reflect.ValueOf(argument) - expectArgumentType := fn.Type().In(index) actualArgumentType := reflect.TypeOf(argument) + var expectArgumentType reflect.Type + if (index == fnArgsNum-1 && fn.Type().In(fnArgsNum-1).Kind() == reflect.Slice) || index > fnArgsNum-1 { + // last fn argument is slice + expectArgumentType = fn.Type().In(fnArgsNum - 1).Elem() // slice element type + + // last argument is also slice, e.g. []int + if actualArgumentType.Kind() == reflect.Slice { + if actualArgumentType.Elem() != expectArgumentType { + err := fmt.Errorf("function argument %d's slice element type is not match, expect %v, actual %v", + index, expectArgumentType, actualArgumentType) + return nil, err + } + argumentsValue[index] = argumentValue + continue + } + } else { + expectArgumentType = fn.Type().In(index) + } + // type match if expectArgumentType == actualArgumentType { argumentsValue[index] = argumentValue @@ -114,7 +66,6 @@ func convertFirstNArgs(fn reflect.Value, n int, args ...interface{}) ([]reflect. // function argument type not match and not convertible err := fmt.Errorf("function argument %d's type is neither match nor convertible, expect %v, actual %v", index, expectArgumentType, actualArgumentType) - log.Error().Err(err).Msg("convert arguments failed") return nil, err } // convert argument to expect type diff --git a/plugin/shared/call_test.go b/plugin/shared/call_test.go index 4f45fa70..d886f2e0 100644 --- a/plugin/shared/call_test.go +++ b/plugin/shared/call_test.go @@ -168,8 +168,30 @@ func TestCallFuncComplex(t *testing.T) { args: []interface{}{1, 2, []int{3, 4}}, expVal: 10, }, - - // FIXME: return more than 2 values + } + + for _, p := range params { + fn := reflect.ValueOf(p.f) + val, err := CallFunc(fn, p.args...) + if !assert.Equal(t, p.expErr, err) { + t.Fatal(err) + } + if !assert.Equal(t, p.expVal, val) { + t.Fatal() + } + } + +} + +func TestCallFuncAbnormal(t *testing.T) { + params := []data{ + // return more than 2 values + { + f: func() (int, int, error) { return 1, 2, nil }, + args: []interface{}{}, + expVal: nil, + expErr: fmt.Errorf("function should return at most 2 values"), + }, } for _, p := range params {