Skip to content

Commit

Permalink
add dingo.ModuleFunc shorthand
Browse files Browse the repository at this point in the history
  • Loading branch information
bastianccm committed Feb 25, 2020
1 parent de4bf3b commit 08aafd6
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 8 deletions.
5 changes: 5 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -474,3 +474,8 @@ Recently https://github.com/google/go-cloud/tree/master/wire popped out in the g
However, when Dingo was first introduced wire was not a thing, and wire still lacks features dingo provides.
https://gocover.io/github.com/i-love-flamingo/dingo
## ModuleFunc
Dingo has a wrapper for `func(*Injector)` called `ModuleFunc`. It is possible to wrap a function with the `ModuleFunc` to become a `Module`.
This is similar to the `http` Packages `HandlerFunc` mechanism and allows to save code and easier set up small projects.
34 changes: 26 additions & 8 deletions module.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
package dingo

import (
"errors"
"fmt"
"reflect"
)

type (
// Module is provided by packages to generate the DI tree
// Module is default entry point for dingo Modules.
// The Configure method is called once during initialization
// and let's the module setup Bindings for the provided Injector.
Module interface {
Configure(injector *Injector)
}

// Depender defines a dependency-aware module
// ModuleFunc wraps a func(injector *Injector) for dependency injection.
// This allows using small functions as dingo Modules.
// The same concept is http.HandlerFunc for http.Handler.
ModuleFunc func(injector *Injector)

// Depender returns a list of Modules via the Depends method.
// This allows a module to specify dependencies, which will be loaded before the actual Module is loaded.
Depender interface {
Depends() []Module
}
)

// Configure call the original ModuleFunc with the given *Injector.
func (f ModuleFunc) Configure(injector *Injector) {
f(injector)
}

// TryModule tests if modules are properly bound
func TryModule(modules ...Module) (resultingError error) {
defer func() {
Expand All @@ -26,7 +38,7 @@ func TryModule(modules ...Module) (resultingError error) {
resultingError = err
return
}
resultingError = errors.New(fmt.Sprint(err))
resultingError = fmt.Errorf("dingo.TryModule panic: %q", err)
}
}()

Expand All @@ -38,20 +50,26 @@ func TryModule(modules ...Module) (resultingError error) {
return injector.InitModules(modules...)
}

var typeOfModuleFunc = reflect.TypeOf(ModuleFunc(nil))

// resolveDependencies tries to get a complete list of all modules, including all dependencies
// known can be empty initially, and will then be used for subsequent recursive calls
func resolveDependencies(modules []Module, known map[reflect.Type]struct{}) []Module {
func resolveDependencies(modules []Module, known map[interface{}]struct{}) []Module {
final := make([]Module, 0, len(modules))

if known == nil {
known = make(map[reflect.Type]struct{})
known = make(map[interface{}]struct{})
}

for _, module := range modules {
if _, ok := known[reflect.TypeOf(module)]; ok {
var identity interface{} = reflect.TypeOf(module)
if identity == typeOfModuleFunc {
identity = reflect.ValueOf(module)
}
if _, ok := known[identity]; ok {
continue
}
known[reflect.TypeOf(module)] = struct{}{}
known[identity] = struct{}{}
if depender, ok := module.(Depender); ok {
final = append(final, resolveDependencies(depender.Depends(), known)...)
}
Expand Down
25 changes: 25 additions & 0 deletions module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,31 @@ func TestTryModule(t *testing.T) {
assert.Error(t, TryModule(new(tryModulePanic)))
}

func TestResolveDependenciesWithModuleFunc(t *testing.T) {
var countInline, countExtern int

ext := ModuleFunc(func(injector *Injector) {
countExtern++
})

injector, err := NewInjector(
new(tryModuleOk),
ModuleFunc(func(injector *Injector) {
countInline++
}),
ModuleFunc(func(injector *Injector) {
countInline++
}),
ext,
ext,
)

assert.NoError(t, err)
assert.NotNil(t, injector)
assert.Equal(t, 2, countInline, "inline modules should be called once (eventually twice for this test)")
assert.Equal(t, 1, countExtern, "variable defined modules should only be called once")
}

type (
resolveDependenciesModuleA struct{}
resolveDependenciesModuleB struct{}
Expand Down

0 comments on commit 08aafd6

Please sign in to comment.