qbtrade/pkg/dynamic/call.go

156 lines
3.3 KiB
Go
Raw Normal View History

2024-06-27 14:42:38 +00:00
package dynamic
import (
"errors"
"reflect"
)
// CallStructFieldsMethod iterates field from the given struct object
// check if the field object implements the interface, if it's implemented, then we call a specific method
func CallStructFieldsMethod(m interface{}, method string, args ...interface{}) error {
rv := reflect.ValueOf(m)
rt := reflect.TypeOf(m)
if rt.Kind() != reflect.Ptr {
return errors.New("the given object needs to be a pointer")
}
rv = rv.Elem()
rt = rt.Elem()
if rt.Kind() != reflect.Struct {
return errors.New("the given object needs to be struct")
}
argValues := ToReflectValues(args...)
for i := 0; i < rt.NumField(); i++ {
fieldType := rt.Field(i)
fieldValue := rv.Field(i)
// skip non-exported fields
if !fieldType.IsExported() {
continue
}
if fieldType.Type.Kind() == reflect.Ptr && fieldValue.IsNil() {
continue
}
methodType, ok := fieldType.Type.MethodByName(method)
if !ok {
continue
}
if len(argValues) < methodType.Type.NumIn() {
// return fmt.Errorf("method %v require %d args, %d given", methodType, methodType.Type.NumIn(), len(argValues))
}
refMethod := fieldValue.MethodByName(method)
refMethod.Call(argValues)
}
return nil
}
// CallMatch calls the function with the matched argument automatically
// you can define multiple parameter factory function to inject the return value as the function argument.
// e.g.,
// CallMatch(targetFunction, 1, 10, true, func() *ParamType { .... })
//
func CallMatch(f interface{}, objects ...interface{}) ([]reflect.Value, error) {
fv := reflect.ValueOf(f)
ft := reflect.TypeOf(f)
var startIndex = 0
var fArgs []reflect.Value
var factoryParams = findFactoryParams(objects...)
nextDynamicInputArg:
for i := 0; i < ft.NumIn(); i++ {
at := ft.In(i)
// uat == underlying argument type
uat := at
if at.Kind() == reflect.Ptr {
uat = at.Elem()
}
for oi := startIndex; oi < len(objects); oi++ {
var obj = objects[oi]
var objT = reflect.TypeOf(obj)
if objT == at {
fArgs = append(fArgs, reflect.ValueOf(obj))
startIndex = oi + 1
continue nextDynamicInputArg
}
// get the kind of argument
switch k := uat.Kind(); k {
case reflect.Interface:
if objT.Implements(at) {
fArgs = append(fArgs, reflect.ValueOf(obj))
startIndex = oi + 1
continue nextDynamicInputArg
}
}
}
// factory param can be reused
for _, fp := range factoryParams {
fpt := fp.Type()
outType := fpt.Out(0)
if outType == at {
fOut := fp.Call(nil)
fArgs = append(fArgs, fOut[0])
continue nextDynamicInputArg
}
}
fArgs = append(fArgs, reflect.Zero(at))
}
out := fv.Call(fArgs)
if ft.NumOut() == 0 {
return out, nil
}
// try to get the error object from the return value (if any)
var err error
for i := 0; i < ft.NumOut(); i++ {
outType := ft.Out(i)
switch outType.Kind() {
case reflect.Interface:
o := out[i].Interface()
switch ov := o.(type) {
case error:
err = ov
}
}
}
return out, err
}
func findFactoryParams(objs ...interface{}) (fs []reflect.Value) {
for i := range objs {
obj := objs[i]
objT := reflect.TypeOf(obj)
if objT.Kind() != reflect.Func {
continue
}
if objT.NumOut() == 0 || objT.NumIn() > 0 {
continue
}
fs = append(fs, reflect.ValueOf(obj))
}
return fs
}