I can use an extra pair of eyes solving this challenge, playground here
Ultimate goal is to register functions and struct public functions into an activity manager and execute them via function name, so something along the lines of:
pool := map[string]interface{
"Sample": func(ctx context.Context) error,
"Sample2": func(ctx context.Context, args ...interface{}) error,
"SampleFromStruct": func(ctx context.Context) error,
"Sample2FromStruct": func(ctx context.Context, args ...interface{}) error,
}
the functions looks like:
func Sample(ctx context.Context) error {
fmt.Println("exec Sample")
return nil
}
func Sample2(ctx context.Context, args interface{}) error {
arguments := struct {
Foo string `json:"foo"`
Bar string `json:"bar"`
}{}
b, err := json.Marshal(args)
if err != nil {
return err
}
if err := json.Unmarshal(b, &arguments); err != nil {
return err
}
fmt.Println("exec Sample2 with args", arguments)
return nil
}
// and same but with struct
type ActivityInStruct struct {
Bar string
}
func (a *ActivityInStruct) SampleInStruct(ctx context.Context) error {
fmt.Println("Value of Bar", a.Bar)
return Sample(ctx)
}
func (a *ActivityInStruct) Sample2InStruct(ctx context.Context, args interface{}) error {
fmt.Println("Value of Bar", a.Bar)
return Sample2(ctx, args)
}
Said this, I got it working with functions with the followed implementation:
type activityManager struct {
fnStorage map[string]interface{}
}
func (lm *activityManager) Register(fn interface{}) error {
fnName := strings.Split((runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()), ".")
name := fnName[len(fnName)-1]
lm.fnStorage[name] = fn
return nil
}
func (lm *activityManager) Exec(ctx context.Context, fnName string, args ...interface{}) error {
fn, ok := lm.fnStorage[fnName]
if !ok {
return fmt.Errorf("activity %s not found", fnName)
}
if signatureCtx, ok := fn.(func(context.Context) error); ok {
return signatureCtx(ctx)
}
if signatureWithArgument, ok := fn.(func(context.Context, interface{}) error); ok {
return signatureWithArgument(ctx, args[0])
}
return fmt.Errorf("signature for %s not supported", fnName)
}
so the Execution looks like this:
func NewManager() *activityManager {
return &activityManager{
fnStorage: map[string]interface{}{},
}
}
/*...*/
ctx := context.Background()
manager := NewManager()
manager.Register(Sample)
manager.Register(Sample2)
if err := manager.Exec(ctx, "Sample"); err != nil {
fmt.Println("Sample error", err.Error())
return
}
args1 := map[string]interface{}{
"foo": "isFoo",
"bar": "isBar",
}
if err := manager.Exec(ctx, "Sample2", args1); err != nil {
fmt.Println("Sample2 error", err.Error())
return
}
However, to register something like this:
func (lm *activityManager) RegisterStruct(fn interface{}) error {
t := reflect.TypeOf(fn)
for i := 0; i < t.NumMethod(); i {
m := t.Method(i)
if m.IsExported() {
/*
This won't work cause m.Type are
func(*main.ActivityInStruct, context.Context, interface {}) error
func(*main.ActivityInStruct, context.Context) error
instead of
func(context.Context, interface {}) error
func(context.Context) error
*/
lm.fnStorage[m.Name] = m.Func
}
}
return nil
}
/* Register Activity from Public methods in struct */
activitiesStruct := &ActivityInStruct{
Bar: "I'm the Bar",
}
manager.RegisterStruct(activitiesStruct)
I cant get this to work cause the reflection shows the method signature like this instead, func(*main.ActivityInStruct, context.Context, interface {}) error
Any idea how to go around that? the full playground is here
CodePudding user response:
Call Value.Method to get the method value.
func (lm *activityManager) RegisterStruct(fn interface{}) error {
v := reflect.ValueOf(fn)
t := v.Type()
for i := 0; i < t.NumMethod(); i {
m := t.Method(i)
if m.IsExported() {
lm.fnStorage[m.Name] = v.Method(i).Interface()
}
}
return nil
}