Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to get a generic method signature using reflection in Golang

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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

>Solution :

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
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading