Home > Mobile >  How to get a generic method signature using reflection in Golang
How to get a generic method signature using reflection in Golang

Time:09-15

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
}
  • Related