Skip to content

Commit

Permalink
feat: add module field injection (#234)
Browse files Browse the repository at this point in the history
* chore: upgrade koanf to v1.4.0

* feat: allow module field injection

* fix: strict variable name 🍻

Co-authored-by: Trock <[email protected]>
  • Loading branch information
Reasno and GGXXLL authored Jan 20, 2022
1 parent b1f094d commit eaf7ad9
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 24 deletions.
46 changes: 24 additions & 22 deletions c.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,33 +198,35 @@ func Default(opts ...CoreOption) *C {
return c
}

// AddModule adds one or more module(s) to the core. If any of the variadic
// arguments is an error, it would panic. This makes it easy to consume
// constructors directly, so instead of writing:
//
// component, err := components.New()
// if err != nil {
// panic(err)
// }
// c.AddModule(component)
//
// You can write:
//
// c.AddModule(component.New())
// AddModule adds a module to the core.
//
// A Module is a group of functionality. It must provide some runnable stuff:
// http handlers, grpc handlers, cron jobs, one-time command, etc.
func (c *C) AddModule(modules ...interface{}) {
for i := range modules {
switch x := modules[i].(type) {
case error:
panic(x)
case func():
c.Container.AddModule(cleanup(x))
default:
c.Container.AddModule(x)
//
// Optionally if the module embeds di.In, its fields will be injected by DI
// container. The semantics of injection follows the same rule of dig.Invoke.
// Note that the module added in this way will not retain any original field
// values, i.e. the module will only contain fields populated by DI container.
func (c *C) AddModule(module interface{}) {
t := reflect.TypeOf(module)
if t.Kind() == reflect.Ptr && dig.IsIn(t.Elem()) {
err := di.IntoPopulator(c.di).Populate(module)
if err != nil {
panic(err)
}
c.Container.AddModule(module)
return
}
if dig.IsIn(t) {
copy := reflect.New(t)
err := di.IntoPopulator(c.di).Populate(copy.Interface())
if err != nil {
panic(err)
}
c.Container.AddModule(copy.Elem().Interface())
return
}
c.Container.AddModule(module)
}

// Provide adds dependencies provider to the core. Note the dependency provider
Expand Down
41 changes: 40 additions & 1 deletion c_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func TestC_cleanup(t *testing.T) {
}
}})
c.Invoke(func(_ struct{}) {})
c.AddModule(func() func() {
c.AddModule(func() closer {
return func() {
moduleCleanupCalled = true
}
Expand All @@ -216,3 +216,42 @@ func TestContainer_Shutdown(t *testing.T) {
container.Shutdown()
assert.Equal(t, 1, seq)
}

func TestC_AddModule(t *testing.T) {
type Module struct {
di.In
Int int
Float float64 `optional:"true"`
}
for _, c := range []struct {
name string
module interface{}
assertion func(t *testing.T, c *C)
}{
{
"di.In",
Module{Float: 2.0},
func(t *testing.T, c *C) {
assert.Equal(t, 1, c.Modules()[0].(Module).Int)
assert.Equal(t, 0.0, c.Modules()[0].(Module).Float)
},
},
{
"*di.In",
&Module{Float: 2.0},
func(t *testing.T, c *C) {
assert.Equal(t, 1, c.Modules()[0].(*Module).Int)
assert.Equal(t, 0.0, c.Modules()[0].(*Module).Float)
},
},
} {
t.Run(c.name, func(t *testing.T) {
cc := New()
cc.Provide(di.Deps{func() int {
return 1
}})
cc.AddModule(c.module)
c.assertion(t, cc)
})
}
}
22 changes: 21 additions & 1 deletion example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import (
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"time"

"github.com/DoNewsCode/core"
"github.com/DoNewsCode/core/di"
"github.com/go-kit/log"
"github.com/gorilla/mux"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/providers/basicflag"
Expand All @@ -29,7 +31,7 @@ func ExampleC_AddModuleFunc() {
// core.cleanup core_test.Foo
}

func ExampleC_AddModule() {
func ExampleC_AddModule_simple() {
type Foo struct{}
c := core.New()
c.AddModule(Foo{})
Expand All @@ -38,6 +40,24 @@ func ExampleC_AddModule() {
// core_test.Foo
}

func ExampleC_AddModule_inject() {
type Foo struct {
di.In
Logger log.Logger
}
var f Foo
c := core.New()
c.Provide(di.Deps{func() log.Logger {
return log.NewLogfmtLogger(os.Stdout)
}})
// If the module embeds di.In (directly or indirectly via pointer), then DI will
// populate its fields.
c.AddModule(&f)
f.Logger.Log("msg", "hello")
// Output:
// msg=hello
}

func ExampleC_Provide() {
type Foo struct {
Value string
Expand Down

0 comments on commit eaf7ad9

Please sign in to comment.