package help import ( "fmt" "reflect" "strings" ) // genericType represents a any reflect.Type. type genericType int var genericTp = reflect.TypeOf(new(genericType)).Elem() // functionCache keeps genericFunc reflection objects in cache. type functionCache struct { MethodName string ParamName string FnValue reflect.Value FnType reflect.Type TypesIn []reflect.Type TypesOut []reflect.Type } // genericFunc is a type used to validate and call dynamic functions. type genericFunc struct { Cache *functionCache } // Call calls a dynamic function. func (g *genericFunc) Call(params ...interface{}) interface{} { paramsIn := make([]reflect.Value, len(params)) for i, param := range params { paramsIn[i] = reflect.ValueOf(param) } paramsOut := g.Cache.FnValue.Call(paramsIn) if len(paramsOut) >= 1 { return paramsOut[0].Interface() } return nil } // newGenericFunc instantiates a new genericFunc pointer func newGenericFunc(methodName, paramName string, fn interface{}, validateFunc func(*functionCache) error) (*genericFunc, error) { cache := &functionCache{} cache.FnValue = reflect.ValueOf(fn) if cache.FnValue.Kind() != reflect.Func { return nil, fmt.Errorf("%s: parameter [%s] is not a function type. It is a '%s'", methodName, paramName, cache.FnValue.Type()) } cache.MethodName = methodName cache.ParamName = paramName cache.FnType = cache.FnValue.Type() numTypesIn := cache.FnType.NumIn() cache.TypesIn = make([]reflect.Type, numTypesIn) for i := 0; i < numTypesIn; i++ { cache.TypesIn[i] = cache.FnType.In(i) } numTypesOut := cache.FnType.NumOut() cache.TypesOut = make([]reflect.Type, numTypesOut) for i := 0; i < numTypesOut; i++ { cache.TypesOut[i] = cache.FnType.Out(i) } if err := validateFunc(cache); err != nil { return nil, err } return &genericFunc{Cache: cache}, nil } // simpleParamValidator creates a function to validate genericFunc based in the // In and Out function parameters. func simpleParamValidator(In []reflect.Type, Out []reflect.Type) func(cache *functionCache) error { return func(cache *functionCache) error { var isValid = func() bool { if In != nil { if len(In) != len(cache.TypesIn) { return false } for i, paramIn := range In { if paramIn != genericTp && paramIn != cache.TypesIn[i] { return false } } } if Out != nil { if len(Out) != len(cache.TypesOut) { return false } for i, paramOut := range Out { if paramOut != genericTp && paramOut != cache.TypesOut[i] { return false } } } return true } if !isValid() { return fmt.Errorf("%s: parameter [%s] has a invalid function signature. Expected: '%s', actual: '%s'", cache.MethodName, cache.ParamName, formatFnSignature(In, Out), formatFnSignature(cache.TypesIn, cache.TypesOut)) } return nil } } // newElemTypeSlice creates a slice of items elem types. func newElemTypeSlice(items ...interface{}) []reflect.Type { typeList := make([]reflect.Type, len(items)) for i, item := range items { typeItem := reflect.TypeOf(item) if typeItem.Kind() == reflect.Ptr { typeList[i] = typeItem.Elem() } } return typeList } // formatFnSignature formats the func signature based in the parameters types. func formatFnSignature(In []reflect.Type, Out []reflect.Type) string { paramInNames := make([]string, len(In)) for i, typeIn := range In { if typeIn == genericTp { paramInNames[i] = "T" } else { paramInNames[i] = typeIn.String() } } paramOutNames := make([]string, len(Out)) for i, typeOut := range Out { if typeOut == genericTp { paramOutNames[i] = "T" } else { paramOutNames[i] = typeOut.String() } } return fmt.Sprintf("func(%s)%s", strings.Join(paramInNames, ","), strings.Join(paramOutNames, ",")) }