Skip to content

Commit

Permalink
Add support for getting info from Invoke statements (#383)
Browse files Browse the repository at this point in the history
This commit exposes InvokeInfo and FillInvokeInfo to help users extract the types requested by an Invoke statement.

Example usage -

var info InvokeInfo
c.Invoke(func(typ1 type1, typ2 type2){}, FillInvokeInfo(&info))

// Will print the types requested by the Invoke statement
for _, typ := range info.ReqTypes {
    fmt.Println(typ) 
}

Refs: GO-669
  • Loading branch information
manjari25 committed Apr 24, 2023
1 parent 87cf89e commit 297a20c
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 5 deletions.
91 changes: 91 additions & 0 deletions dig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3741,6 +3741,97 @@ func TestProvideInfoOption(t *testing.T) {
})
}

func TestInvokeInfoOption(t *testing.T) {
t.Parallel()

type type1 struct{}
type params struct {
dig.In

Field1 string
Field2 int
}
type type2 struct{}
c := digtest.New(t)
c.RequireProvide(func() map[string]string { return map[string]string{} })
c.RequireProvide(func() *type1 { return &type1{} })
c.RequireProvide(func() (string, int) {
return "hello", 2023
})
c.RequireProvide(func(s string) *type2 {
if s == "hello" {
return &type2{}
}
return nil
})

tests := []struct {
desc string
invokeFn any
numTyps int
wantStrs []string
}{
{
desc: "one map type requested",
invokeFn: func(m map[string]string) {},
numTyps: 1,
wantStrs: []string{"map[string]string"},
},
{
desc: "one map type, one struct type requested",
invokeFn: func(m map[string]string, typ1 *type1) {},
numTyps: 2,
wantStrs: []string{"map[string]string", "*dig_test.type1"},
},
{
desc: "invoke with param",
invokeFn: func(p params) {},
numTyps: 2,
wantStrs: []string{"string", "int"},
},
{
desc: "invoke with dependencies",
invokeFn: func(typ2 *type2) {},
numTyps: 1,
wantStrs: []string{"*dig_test.type2"},
},
}

for _, tt := range tests {
var info dig.InvokeInfo
c.RequireInvoke(tt.invokeFn, dig.FillInvokeInfo(&info))
require.NotNil(t, info)
require.Len(t, info.Inputs, tt.numTyps)
for i := 0; i < tt.numTyps; i++ {
assert.Equal(t, tt.wantStrs[i], info.Inputs[i].String())
}
}

t.Run("no error on nil InvokeInfo", func(t *testing.T) {
c := digtest.New(t)
c.RequireProvide(func() string { return "" })
c.RequireInvoke(func(s string) {}, dig.FillInvokeInfo(nil))
})
}

func TestFillInvokeInfoString(t *testing.T) {
t.Parallel()

t.Run("nil", func(t *testing.T) {
t.Parallel()

assert.Equal(t, "FillInvokeInfo(0x0)", fmt.Sprint(dig.FillInvokeInfo(nil)))
})

t.Run("not nil", func(t *testing.T) {
t.Parallel()

opt := dig.FillInvokeInfo(new(dig.InvokeInfo))
assert.NotEqual(t, fmt.Sprint(opt), "FillInvokeInfo(0x0)")
assert.Contains(t, fmt.Sprint(opt), "FillInvokeInfo(0x")
})
}

func TestEndToEndSuccessWithAliases(t *testing.T) {
t.Run("pointer constructor", func(t *testing.T) {
type Buffer = *bytes.Buffer
Expand Down
62 changes: 57 additions & 5 deletions invoke.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,48 @@ package dig

import (
"fmt"
"reflect"

"go.uber.org/dig/internal/digreflect"
"go.uber.org/dig/internal/graph"
"reflect"
)

// An InvokeOption modifies the default behavior of Invoke. It's included for
// future functionality; currently, there are no concrete implementations.
// An InvokeOption modifies the default behavior of Invoke.
type InvokeOption interface {
unimplemented()
applyInvokeOption(*invokeOptions)
}

type invokeOptions struct {
Info *InvokeInfo
}

// InvokeInfo provides information about an Invoke.
type InvokeInfo struct {
Inputs []*Input
}

// FillInvokeInfo is an InvokeOption that writes information on the types
// accepted by the Invoke function into the specified InvokeInfo.
// For example:
//
// var info dig.InvokeInfo
// err := c.Invoke(func(string, int){}, dig.FillInvokeInfo(&info))
//
// info.Inputs[0].String() will be string.
// info.Inputs[1].String() will be int.
func FillInvokeInfo(info *InvokeInfo) InvokeOption {
return fillInvokeInfoOption{info: info}
}

type fillInvokeInfoOption struct {
info *InvokeInfo
}

func (o fillInvokeInfoOption) String() string {
return fmt.Sprintf("FillInvokeInfo(%p)", o.info)
}

func (o fillInvokeInfoOption) applyInvokeOption(opts *invokeOptions) {
opts.Info = o.info
}

// Invoke runs the given function after instantiating its dependencies.
Expand Down Expand Up @@ -105,6 +137,26 @@ func (s *Scope) Invoke(function interface{}, opts ...InvokeOption) (err error) {
}()
}

var options invokeOptions
for _, o := range opts {
o.applyInvokeOption(&options)
}

// Record info for the invoke if requested
if info := options.Info; info != nil {
params := pl.DotParam()
info.Inputs = make([]*Input, len(params))
for i, p := range params {
info.Inputs[i] = &Input{
t: p.Type,
optional: p.Optional,
name: p.Name,
group: p.Group,
}
}

}

returned := s.invokerFn(reflect.ValueOf(function), args)
if len(returned) == 0 {
return nil
Expand Down

0 comments on commit 297a20c

Please sign in to comment.