Files
httprunner/plugin/shared/call.go
2022-01-16 00:07:06 +08:00

153 lines
4.5 KiB
Go

package shared
import (
"fmt"
"reflect"
"github.com/rs/zerolog/log"
)
// 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...)
if err != nil {
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++ {
argument := args[index]
if argument == nil {
argumentsValue[index] = reflect.Zero(fn.Type().In(index))
continue
}
argumentValue := reflect.ValueOf(argument)
expectArgumentType := fn.Type().In(index)
actualArgumentType := reflect.TypeOf(argument)
// type match
if expectArgumentType == actualArgumentType {
argumentsValue[index] = argumentValue
continue
}
// type not match, check if convertible
if !actualArgumentType.ConvertibleTo(expectArgumentType) {
// 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
argumentsValue[index] = argumentValue.Convert(expectArgumentType)
}
return argumentsValue, nil
}
func call(fn reflect.Value, args []reflect.Value) (interface{}, error) {
resultValues := fn.Call(args)
if resultValues == nil {
// no returns
return nil, nil
} else if len(resultValues) == 2 {
// return two arguments: interface{}, error
if resultValues[1].Interface() != nil {
return resultValues[0].Interface(), resultValues[1].Interface().(error)
} else {
return resultValues[0].Interface(), nil
}
} else if len(resultValues) == 1 {
// return one argument
if err, ok := resultValues[0].Interface().(error); ok {
// return error
return nil, err
} else {
// return interface{}
return resultValues[0].Interface(), nil
}
} else {
// return more than 2 arguments, unexpected
err := fmt.Errorf("function should return at most 2 values")
return nil, err
}
}