Skip to content

Commit

Permalink
feat(depinject): resolve interface types (#12169)
Browse files Browse the repository at this point in the history
  • Loading branch information
kocubinski authored Jun 9, 2022
1 parent 907df32 commit dd2e432
Show file tree
Hide file tree
Showing 22 changed files with 539 additions and 133 deletions.
6 changes: 5 additions & 1 deletion core/internal/testpb/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,8 @@ type keeperB struct {
a KeeperA
}

type KeeperB interface{}
type KeeperB interface {
isKeeperB()
}

func (k keeperB) isKeeperB() {}
148 changes: 148 additions & 0 deletions depinject/binding_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package depinject_test

import (
"fmt"
"reflect"
"testing"

"github.com/stretchr/testify/require"

"github.com/regen-network/gocuke"
"github.com/stretchr/testify/assert"

"github.com/cosmos/cosmos-sdk/depinject"
)

func TestBindInterface(t *testing.T) {
gocuke.NewRunner(t, &bindingSuite{}).
Path("features/bindings.feature").
Step(`we try to resolve a "Duck" in global scope`, (*bindingSuite).WeTryToResolveADuckInGlobalScope).
Step(`module "(\w+)" wants a "Duck"`, (*bindingSuite).ModuleWantsADuck).
Run()
}

type Duck interface {
quack()
}

type Mallard struct{}
type Canvasback struct{}
type Marbled struct{}

func (duck Mallard) quack() {}
func (duck Canvasback) quack() {}
func (duck Marbled) quack() {}

type DuckWrapper struct {
Module string
Duck Duck
}

func (d DuckWrapper) IsManyPerContainerType() {}

type Pond struct {
Ducks []DuckWrapper
}

type bindingSuite struct {
gocuke.TestingT // this gets injected by gocuke

configs []depinject.Config
pond *Pond
err error
}

func (s bindingSuite) AnInterfaceDuck() {
// we don't need to do anything because this is defined at the type level
}

func (s bindingSuite) TwoImplementationsMallardAndCanvasback() {
// we don't need to do anything because this is defined at the type level
}

func (s *bindingSuite) IsProvided(a string) {
switch a {
case "Mallard":
s.addConfig(depinject.Provide(func() Mallard { return Mallard{} }))
case "Canvasback":
s.addConfig(depinject.Provide(func() Canvasback { return Canvasback{} }))
case "Marbled":
s.addConfig(depinject.Provide(func() Marbled { return Marbled{} }))
default:
s.Fatalf("unexpected duck type %s", a)
}
}

func (s *bindingSuite) addConfig(config depinject.Config) {
s.configs = append(s.configs, config)
}

func (s *bindingSuite) WeTryToResolveADuckInGlobalScope() {
s.addConfig(depinject.Provide(func(duck Duck) DuckWrapper {
return DuckWrapper{Module: "", Duck: duck}
}))
}

func (s *bindingSuite) resolvePond() *Pond {
if s.pond != nil {
return s.pond
}

s.addConfig(depinject.Provide(func(ducks []DuckWrapper) Pond { return Pond{Ducks: ducks} }))
var pond Pond
s.err = depinject.Inject(depinject.Configs(s.configs...), &pond)
s.pond = &pond
return s.pond
}

func (s *bindingSuite) IsResolvedInGlobalScope(typeName string) {
pond := s.resolvePond()
found := false
for _, dw := range pond.Ducks {
if dw.Module == "" {
require.Contains(s, reflect.TypeOf(dw.Duck).Name(), typeName)
found = true
}
}
assert.True(s, found)
}

func (s *bindingSuite) ThereIsAError(expectedErrorMsg string) {
s.resolvePond()
assert.ErrorContains(s, s.err, expectedErrorMsg)
}

func (s *bindingSuite) ThereIsNoError() {
s.resolvePond()
assert.NoError(s, s.err)
}

func fullTypeName(typeName string) string {
return fmt.Sprintf("github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.%s", typeName)
}

func (s *bindingSuite) ThereIsAGlobalBindingForA(preferredType string, interfaceType string) {
s.addConfig(depinject.BindInterface(fullTypeName(interfaceType), fullTypeName(preferredType)))
}

func (s *bindingSuite) ThereIsABindingForAInModule(preferredType string, interfaceType string, moduleName string) {
s.addConfig(depinject.BindInterfaceInModule(moduleName, fullTypeName(interfaceType), fullTypeName(preferredType)))
}

func (s *bindingSuite) ModuleWantsADuck(module string) {
s.addConfig(depinject.ProvideInModule(module, func(duck Duck) DuckWrapper {
return DuckWrapper{Module: module, Duck: duck}
}))
}

func (s *bindingSuite) ModuleResolvesA(module string, duckType string) {
pond := s.resolvePond()
moduleFound := false
for _, dw := range pond.Ducks {
if dw.Module == module {
assert.Contains(s, reflect.TypeOf(dw.Duck).Name(), duckType)
moduleFound = true
}
}
assert.True(s, moduleFound)
}
42 changes: 42 additions & 0 deletions depinject/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,48 @@ func provide(ctr *container, key *moduleKey, providers []interface{}) error {
return nil
}

// BindInterface defines a container configuration for an explicit interface binding of inTypeName to outTypeName
// in global scope. The example below demonstrates a configuration where the container always provides a Canvasback
// instance when an interface of type Duck is requested as an input.
//
// BindInterface(
// "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck",
// "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Canvasback")
func BindInterface(inTypeName string, outTypeName string) Config {
return containerConfig(func(ctr *container) error {
return bindInterface(ctr, inTypeName, outTypeName, "")
})
}

// BindInterfaceInModule defines a container configuration for an explicit interface binding of inTypeName to outTypeName
// in the scope of the module with name moduleName. The example below demonstrates a configuration where the container
// provides a Canvasback instance when an interface of type Duck is requested as an input, but only in the scope of
// "moduleFoo".
//
// BindInterfaceInModule(
// "moduleFoo",
// "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck",
// "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Canvasback")
func BindInterfaceInModule(moduleName string, inTypeName string, outTypeName string) Config {
return containerConfig(func(ctr *container) error {
return bindInterface(ctr, inTypeName, outTypeName, moduleName)
})
}

func bindInterface(ctr *container, inTypeName string, outTypeName string, moduleName string) error {
var mk *moduleKey
if moduleName != "" {
mk = &moduleKey{name: moduleName}
}
ctr.addBinding(interfaceBinding{
interfaceName: inTypeName,
implTypeName: outTypeName,
moduleKey: mk,
})

return nil
}

func Supply(values ...interface{}) Config {
loc := LocationFromCaller(1)
return containerConfig(func(ctr *container) error {
Expand Down
Loading

0 comments on commit dd2e432

Please sign in to comment.