From a880bc0e166f52576f1bca4eefa9635c8ba60293 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 6 Jun 2022 09:33:23 -0500 Subject: [PATCH 01/41] Revert "feat(depinject): key resolvers for interface types (#12103)" This reverts commit c4934b7bab2304944be4c2762f5a1081f183f6e9. --- depinject/container.go | 39 +++++++++--------------------- depinject/container_test.go | 42 --------------------------------- depinject/provider_desc.go | 2 -- depinject/provider_desc_test.go | 24 ------------------- depinject/struct_args.go | 14 ----------- 5 files changed, 11 insertions(+), 110 deletions(-) diff --git a/depinject/container.go b/depinject/container.go index 37580c051b07..0f46b57f9fbd 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -13,8 +13,7 @@ import ( type container struct { *debugConfig - resolvers map[reflect.Type]resolver - keyedResolvers map[string]resolver + resolvers map[reflect.Type]resolver moduleKeys map[string]*moduleKey @@ -30,12 +29,11 @@ type resolveFrame struct { func newContainer(cfg *debugConfig) *container { return &container{ - debugConfig: cfg, - resolvers: map[reflect.Type]resolver{}, - keyedResolvers: map[string]resolver{}, - moduleKeys: map[string]*moduleKey{}, - callerStack: nil, - callerMap: map[Location]bool{}, + debugConfig: cfg, + resolvers: map[reflect.Type]resolver{}, + moduleKeys: map[string]*moduleKey{}, + callerStack: nil, + callerMap: map[Location]bool{}, } } @@ -78,13 +76,7 @@ func (c *container) call(provider *ProviderDescriptor, moduleKey *moduleKey) ([] return out, nil } -func (c *container) getResolver(typ reflect.Type, key string) (resolver, error) { - if key != "" { - if vr, ok := c.keyedResolvers[key]; ok { - return vr, nil - } - } - +func (c *container) getResolver(typ reflect.Type) (resolver, error) { if vr, ok := c.resolvers[typ]; ok { return vr, nil } @@ -155,7 +147,7 @@ func (c *container) addNode(provider *ProviderDescriptor, key *moduleKey) (inter return nil, fmt.Errorf("one-per-module type %v can't be used as an input parameter", typ) } - vr, err := c.getResolver(typ, in.Key) + vr, err := c.getResolver(typ) if err != nil { return nil, err } @@ -197,7 +189,7 @@ func (c *container) addNode(provider *ProviderDescriptor, key *moduleKey) (inter typ = typ.Elem() } - vr, err := c.getResolver(typ, out.Key) + vr, err := c.getResolver(typ) if err != nil { return nil, err } @@ -219,10 +211,6 @@ func (c *container) addNode(provider *ProviderDescriptor, key *moduleKey) (inter idxInValues: i, } c.resolvers[typ] = vr - - if out.Key != "" { - c.keyedResolvers[out.Key] = vr - } } c.addGraphEdge(providerGraphNode, vr.typeGraphNode()) @@ -257,18 +245,13 @@ func (c *container) addNode(provider *ProviderDescriptor, key *moduleKey) (inter } typeGraphNode := c.typeGraphNode(typ) - mdr := &moduleDepResolver{ + c.resolvers[typ] = &moduleDepResolver{ typ: typ, idxInValues: i, node: node, valueMap: map[*moduleKey]reflect.Value{}, graphNode: typeGraphNode, } - c.resolvers[typ] = mdr - - if out.Key != "" { - c.keyedResolvers[out.Key] = mdr - } c.addGraphEdge(providerGraphNode, typeGraphNode) } @@ -321,7 +304,7 @@ func (c *container) resolve(in ProviderInput, moduleKey *moduleKey, caller Locat return reflect.ValueOf(OwnModuleKey{moduleKey}), nil } - vr, err := c.getResolver(in.Type, in.Key) + vr, err := c.getResolver(in.Type) if err != nil { return reflect.Value{}, err } diff --git a/depinject/container_test.go b/depinject/container_test.go index 25af05504010..c1db90d77945 100644 --- a/depinject/container_test.go +++ b/depinject/container_test.go @@ -634,45 +634,3 @@ func TestConditionalDebugging(t *testing.T) { require.Empty(t, logs) require.True(t, success) } - -type Duck interface { - quack() -} - -type AlsoDuck interface { - quack() -} - -type Mallard struct{} - -func (duck Mallard) quack() {} - -type KeyedOutput struct { - depinject.Out - Duck Duck `key:"foo"` -} - -type KeyedInput struct { - depinject.In - AlsoDuck AlsoDuck `key:"foo"` -} - -type Pond struct { - Duck AlsoDuck -} - -func TestKeyedInputOutput(t *testing.T) { - var pond Pond - - require.NoError(t, - depinject.Inject( - depinject.Provide( - func() KeyedOutput { return KeyedOutput{Duck: Mallard{}} }, - func(in KeyedInput) Pond { - require.NotNil(t, in.AlsoDuck) - return Pond{Duck: in.AlsoDuck} - }), - &pond)) - - require.NotNil(t, pond) -} diff --git a/depinject/provider_desc.go b/depinject/provider_desc.go index 84e8cffff3d6..bf39e2a31873 100644 --- a/depinject/provider_desc.go +++ b/depinject/provider_desc.go @@ -28,12 +28,10 @@ type ProviderDescriptor struct { type ProviderInput struct { Type reflect.Type Optional bool - Key string } type ProviderOutput struct { Type reflect.Type - Key string } func ExtractProviderDescriptor(provider interface{}) (ProviderDescriptor, error) { diff --git a/depinject/provider_desc_test.go b/depinject/provider_desc_test.go index 014bd82991ec..26a478e70762 100644 --- a/depinject/provider_desc_test.go +++ b/depinject/provider_desc_test.go @@ -24,16 +24,6 @@ type StructOut struct { Y []byte } -type KeyedIn struct { - depinject.In - X string `key:"theKey"` -} - -type KeyedOut struct { - depinject.Out - X string `key:"theKey"` -} - func TestExtractProviderDescriptor(t *testing.T) { var ( intType = reflect.TypeOf(0) @@ -97,20 +87,6 @@ func TestExtractProviderDescriptor(t *testing.T) { nil, true, }, - { - name: "keyed input", - ctr: func(_ KeyedIn) int { return 0 }, - wantIn: []depinject.ProviderInput{{Type: stringType, Key: "theKey"}}, - wantOut: []depinject.ProviderOutput{{Type: intType}}, - wantErr: false, - }, - { - name: "keyed output", - ctr: func(s string) KeyedOut { return KeyedOut{X: "foo"} }, - wantIn: []depinject.ProviderInput{{Type: stringType}}, - wantOut: []depinject.ProviderOutput{{Type: stringType, Key: "theKey"}}, - wantErr: false, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/depinject/struct_args.go b/depinject/struct_args.go index 8310704eb341..5de9eea7280f 100644 --- a/depinject/struct_args.go +++ b/depinject/struct_args.go @@ -120,16 +120,9 @@ func structArgsInTypes(typ reflect.Type) ([]ProviderInput, error) { } } - var key string - keyTag, keyFound := f.Tag.Lookup("key") - if keyFound { - key = keyTag - } - res = append(res, ProviderInput{ Type: f.Type, Optional: optional, - Key: key, }) } return res, nil @@ -158,15 +151,8 @@ func structArgsOutTypes(typ reflect.Type) []ProviderOutput { continue } - var key string - keyTag, keyFound := f.Tag.Lookup("key") - if keyFound { - key = keyTag - } - res = append(res, ProviderOutput{ Type: f.Type, - Key: key, }) } return res From f88a6711025044fef11ebfb488c57c42d4843922 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 6 Jun 2022 17:23:11 -0500 Subject: [PATCH 02/41] feat(depinject): auto resolve interface types --- depinject/container.go | 26 +++++++++++++++++++++----- x/auth/module.go | 2 +- x/bank/module.go | 4 ++-- x/feegrant/module/module.go | 4 ++-- x/staking/module.go | 6 +++--- 5 files changed, 29 insertions(+), 13 deletions(-) diff --git a/depinject/container.go b/depinject/container.go index 0f46b57f9fbd..1e71614ef03c 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -3,11 +3,9 @@ package depinject import ( "bytes" "fmt" - "reflect" - - "github.com/pkg/errors" - "github.com/cosmos/cosmos-sdk/depinject/internal/graphviz" + "github.com/pkg/errors" + "reflect" ) type container struct { @@ -77,6 +75,7 @@ func (c *container) call(provider *ProviderDescriptor, moduleKey *moduleKey) ([] } func (c *container) getResolver(typ reflect.Type) (resolver, error) { + c.logf("Resolving %v", typ) if vr, ok := c.resolvers[typ]; ok { return vr, nil } @@ -122,7 +121,24 @@ func (c *container) getResolver(typ reflect.Type) (resolver, error) { c.resolvers[mapType] = &mapOfOnePerModuleResolver{r} } - return c.resolvers[typ], nil + res := c.resolvers[typ] + + if res == nil && typ.Kind() == reflect.Interface { + var found bool + for k, r := range c.resolvers { + if k.Implements(typ) { + c.logf("Found candidate type %v implementing %v", k, typ) + if found { + // TODO provide example YAML binding when the implementation is done + panic(fmt.Sprintf(" Multiple implementations found for interface %v. Specify an explicit type binding in app.yaml", typ)) + } + res = r + found = true + } + } + } + + return res, nil } var stringType = reflect.TypeOf("") diff --git a/x/auth/module.go b/x/auth/module.go index feec8ccaccea..e15370ee1163 100644 --- a/x/auth/module.go +++ b/x/auth/module.go @@ -211,7 +211,7 @@ func provideModuleBasic() runtime.AppModuleBasicWrapper { type authOutputs struct { depinject.Out - AccountKeeper keeper.AccountKeeper `key:"cosmos.auth.v1.AccountKeeper"` + AccountKeeper keeper.AccountKeeper Module runtime.AppModuleWrapper } diff --git a/x/bank/module.go b/x/bank/module.go index 687f1a39d55f..5566827e7046 100644 --- a/x/bank/module.go +++ b/x/bank/module.go @@ -221,7 +221,7 @@ type bankInputs struct { depinject.In Config *modulev1.Module - AccountKeeper types.AccountKeeper `key:"cosmos.auth.v1.AccountKeeper"` + AccountKeeper types.AccountKeeper Cdc codec.Codec Subspace paramtypes.Subspace Key *store.KVStoreKey @@ -230,7 +230,7 @@ type bankInputs struct { type bankOutputs struct { depinject.Out - BankKeeper keeper.Keeper `key:"cosmos.bank.v1.Keeper"` + BankKeeper keeper.Keeper Module runtime.AppModuleWrapper } diff --git a/x/feegrant/module/module.go b/x/feegrant/module/module.go index d712e102c84b..2788ed204b0c 100644 --- a/x/feegrant/module/module.go +++ b/x/feegrant/module/module.go @@ -209,8 +209,8 @@ type feegrantInputs struct { Key *store.KVStoreKey Cdc codec.Codec - AccountKeeper feegrant.AccountKeeper `key:"cosmos.auth.v1.AccountKeeper"` - BankKeeper feegrant.BankKeeper `key:"cosmos.bank.v1.Keeper"` + AccountKeeper feegrant.AccountKeeper + BankKeeper feegrant.BankKeeper Registry cdctypes.InterfaceRegistry } diff --git a/x/staking/module.go b/x/staking/module.go index 8e35603cc486..0eed87d059e0 100644 --- a/x/staking/module.go +++ b/x/staking/module.go @@ -197,8 +197,8 @@ type stakingInputs struct { depinject.In Config *modulev1.Module - AccountKeeper types.AccountKeeper `key:"cosmos.auth.v1.AccountKeeper"` - BankKeeper types.BankKeeper `key:"cosmos.bank.v1.Keeper"` + AccountKeeper types.AccountKeeper + BankKeeper types.BankKeeper Cdc codec.Codec Subspace paramstypes.Subspace Key *store.KVStoreKey @@ -208,7 +208,7 @@ type stakingInputs struct { type stakingOutputs struct { depinject.Out - StakingKeeper *keeper.Keeper `key:"cosmos.staking.v1.Keeper"` + StakingKeeper *keeper.Keeper Module runtime.AppModuleWrapper } From 73fd6d1e429dcf0fe53ff32a06b8d5cc1ad2d4eb Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 6 Jun 2022 17:31:05 -0500 Subject: [PATCH 03/41] Include test exercising interface binding. --- depinject/container_test.go | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/depinject/container_test.go b/depinject/container_test.go index c1db90d77945..d8fa228e7c22 100644 --- a/depinject/container_test.go +++ b/depinject/container_test.go @@ -634,3 +634,45 @@ func TestConditionalDebugging(t *testing.T) { require.Empty(t, logs) require.True(t, success) } + +type Duck interface { + quack() +} + +type AlsoDuck interface { + quack() +} + +type Mallard struct{} + +func (duck Mallard) quack() {} + +type KeyedOutput struct { + depinject.Out + Duck Duck +} + +type KeyedInput struct { + depinject.In + AlsoDuck AlsoDuck +} + +type Pond struct { + Duck AlsoDuck +} + +func TestKeyedInputOutput(t *testing.T) { + var pond Pond + + require.NoError(t, + depinject.Inject( + depinject.Provide( + func() KeyedOutput { return KeyedOutput{Duck: Mallard{}} }, + func(in KeyedInput) Pond { + require.NotNil(t, in.AlsoDuck) + return Pond{Duck: in.AlsoDuck} + }), + &pond)) + + require.NotNil(t, pond) +} From 613592a917f6e91edfab097fece122ebdf20ac23 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 6 Jun 2022 17:39:46 -0500 Subject: [PATCH 04/41] Clean up error message --- depinject/container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depinject/container.go b/depinject/container.go index 1e71614ef03c..bf48cbe83709 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -127,7 +127,7 @@ func (c *container) getResolver(typ reflect.Type) (resolver, error) { var found bool for k, r := range c.resolvers { if k.Implements(typ) { - c.logf("Found candidate type %v implementing %v", k, typ) + c.logf("Found candidate resolver, implicitly binding interface %v to implementing type %v.", typ, k) if found { // TODO provide example YAML binding when the implementation is done panic(fmt.Sprintf(" Multiple implementations found for interface %v. Specify an explicit type binding in app.yaml", typ)) From 5b2f21271cca930573f093f1a4deb234420a152c Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 6 Jun 2022 17:41:58 -0500 Subject: [PATCH 05/41] Clean up error message --- depinject/container.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/depinject/container.go b/depinject/container.go index bf48cbe83709..302a3092eaca 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -130,7 +130,9 @@ func (c *container) getResolver(typ reflect.Type) (resolver, error) { c.logf("Found candidate resolver, implicitly binding interface %v to implementing type %v.", typ, k) if found { // TODO provide example YAML binding when the implementation is done - panic(fmt.Sprintf(" Multiple implementations found for interface %v. Specify an explicit type binding in app.yaml", typ)) + msg := fmt.Sprintf(" Multiple implementations found for interface %v. Specify an explicit type binding in app.yaml", typ) + c.logf(msg) + panic(msg) } res = r found = true From 39e88a78dc2b8baf0a4aeb75ad807fdd8ab0196b Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 6 Jun 2022 18:21:11 -0500 Subject: [PATCH 06/41] Typed error message and improve tests --- depinject/container.go | 9 +++++---- depinject/container_test.go | 34 ++++++++++++++++++---------------- depinject/errors.go | 10 ++++++++++ 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/depinject/container.go b/depinject/container.go index 302a3092eaca..be940dbc040b 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -129,15 +129,16 @@ func (c *container) getResolver(typ reflect.Type) (resolver, error) { if k.Implements(typ) { c.logf("Found candidate resolver, implicitly binding interface %v to implementing type %v.", typ, k) if found { - // TODO provide example YAML binding when the implementation is done - msg := fmt.Sprintf(" Multiple implementations found for interface %v. Specify an explicit type binding in app.yaml", typ) - c.logf(msg) - panic(msg) + return nil, &ErrMultipleImplicitInterfaceBindings{Interface: typ} } res = r found = true } } + + if found { + c.resolvers[typ] = res + } } return res, nil diff --git a/depinject/container_test.go b/depinject/container_test.go index d8fa228e7c22..d1690ea48256 100644 --- a/depinject/container_test.go +++ b/depinject/container_test.go @@ -644,35 +644,37 @@ type AlsoDuck interface { } type Mallard struct{} +type Canvasback struct{} -func (duck Mallard) quack() {} - -type KeyedOutput struct { - depinject.Out - Duck Duck -} - -type KeyedInput struct { - depinject.In - AlsoDuck AlsoDuck -} +func (duck Mallard) quack() {} +func (duck Canvasback) quack() {} type Pond struct { Duck AlsoDuck } -func TestKeyedInputOutput(t *testing.T) { +func TestImplicitBindings(t *testing.T) { var pond Pond require.NoError(t, depinject.Inject( depinject.Provide( - func() KeyedOutput { return KeyedOutput{Duck: Mallard{}} }, - func(in KeyedInput) Pond { - require.NotNil(t, in.AlsoDuck) - return Pond{Duck: in.AlsoDuck} + func() Mallard { return Mallard{} }, + func(duck Duck) Pond { + require.NotNil(t, duck) + return Pond{Duck: duck} }), &pond)) require.NotNil(t, pond) + + require.Error(t, + depinject.Inject( + depinject.Provide( + func() Mallard { return Mallard{} }, + func() Canvasback { return Canvasback{} }, + func(duck Duck) Pond { + return Pond{Duck: duck} + }), + &pond)) } diff --git a/depinject/errors.go b/depinject/errors.go index 7e70cf70482b..f671f186fdba 100644 --- a/depinject/errors.go +++ b/depinject/errors.go @@ -1,11 +1,21 @@ package depinject import ( + "fmt" "reflect" "github.com/pkg/errors" ) +type ErrMultipleImplicitInterfaceBindings struct { + Interface reflect.Type + Err error +} + +func (err ErrMultipleImplicitInterfaceBindings) Error() string { + return fmt.Sprintf("Multiple implementations found for interface %v", err.Interface) +} + func duplicateDefinitionError(typ reflect.Type, duplicateLoc Location, existingLoc string) error { return errors.Errorf("duplicate provision of type %v by %s\n\talready provided by %s", typ, duplicateLoc, existingLoc) From 5731a852fd4c81c4cd021c37f6b0f7dd4167f2e9 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 6 Jun 2022 19:03:33 -0500 Subject: [PATCH 07/41] Never auto-bind an interface to an interface --- depinject/container.go | 2 +- x/bank/module.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depinject/container.go b/depinject/container.go index be940dbc040b..1ec5936c5d9b 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -126,7 +126,7 @@ func (c *container) getResolver(typ reflect.Type) (resolver, error) { if res == nil && typ.Kind() == reflect.Interface { var found bool for k, r := range c.resolvers { - if k.Implements(typ) { + if k.Kind() != reflect.Interface && k.Implements(typ) { c.logf("Found candidate resolver, implicitly binding interface %v to implementing type %v.", typ, k) if found { return nil, &ErrMultipleImplicitInterfaceBindings{Interface: typ} diff --git a/x/bank/module.go b/x/bank/module.go index 5566827e7046..55e106c4976e 100644 --- a/x/bank/module.go +++ b/x/bank/module.go @@ -230,7 +230,7 @@ type bankInputs struct { type bankOutputs struct { depinject.Out - BankKeeper keeper.Keeper + BankKeeper keeper.BaseKeeper Module runtime.AppModuleWrapper } From e8d085f7706587c26b62668770d665358acab378 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 7 Jun 2022 15:04:22 -0500 Subject: [PATCH 08/41] Add Prefer and PreferInModule to depinject API --- depinject/config.go | 24 ++++++++++++++ depinject/container.go | 48 ++++++++++++++++++++-------- depinject/container_test.go | 64 ++++++++++++++++++++++++++++++++----- depinject/errors.go | 23 +++++++++++-- depinject/preference.go | 37 +++++++++++++++++++++ 5 files changed, 172 insertions(+), 24 deletions(-) create mode 100644 depinject/preference.go diff --git a/depinject/config.go b/depinject/config.go index 1043f63adb69..117a5f09d132 100644 --- a/depinject/config.go +++ b/depinject/config.go @@ -48,6 +48,30 @@ func provide(ctr *container, key *moduleKey, providers []interface{}) error { return nil } +// Prefer defines a container configuration +func Prefer(inTypeName string, outTypeName string) Config { + return containerConfig(func(ctr *container) error { + return prefer(ctr, inTypeName, outTypeName, "") + }) +} + +// PreferInModule defines a container configuration +func PreferInModule(moduleName string, inTypeName string, outTypeName string) Config { + return containerConfig(func(ctr *container) error { + return prefer(ctr, inTypeName, outTypeName, moduleName) + }) +} + +func prefer(ctr *container, inTypeName string, outTypeName string, moduleName string) error { + ctr.addPreference(Preference{ + Interface: inTypeName, + Implementation: outTypeName, + ModuleName: moduleName, + }) + + return nil +} + func Supply(values ...interface{}) Config { loc := LocationFromCaller(1) return containerConfig(func(ctr *container) error { diff --git a/depinject/container.go b/depinject/container.go index 1ec5936c5d9b..14b5cfec9cc4 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -5,13 +5,15 @@ import ( "fmt" "github.com/cosmos/cosmos-sdk/depinject/internal/graphviz" "github.com/pkg/errors" + "golang.org/x/exp/slices" "reflect" ) type container struct { *debugConfig - resolvers map[reflect.Type]resolver + resolvers map[reflect.Type]resolver + preferences []Preference moduleKeys map[string]*moduleKey @@ -30,6 +32,7 @@ func newContainer(cfg *debugConfig) *container { debugConfig: cfg, resolvers: map[reflect.Type]resolver{}, moduleKeys: map[string]*moduleKey{}, + preferences: []Preference{}, callerStack: nil, callerMap: map[Location]bool{}, } @@ -74,7 +77,7 @@ func (c *container) call(provider *ProviderDescriptor, moduleKey *moduleKey) ([] return out, nil } -func (c *container) getResolver(typ reflect.Type) (resolver, error) { +func (c *container) getResolver(typ reflect.Type, key *moduleKey) (resolver, error) { c.logf("Resolving %v", typ) if vr, ok := c.resolvers[typ]; ok { return vr, nil @@ -124,19 +127,32 @@ func (c *container) getResolver(typ reflect.Type) (resolver, error) { res := c.resolvers[typ] if res == nil && typ.Kind() == reflect.Interface { - var found bool - for k, r := range c.resolvers { + var matches []reflect.Type + for k := range c.resolvers { if k.Kind() != reflect.Interface && k.Implements(typ) { - c.logf("Found candidate resolver, implicitly binding interface %v to implementing type %v.", typ, k) - if found { - return nil, &ErrMultipleImplicitInterfaceBindings{Interface: typ} - } - res = r - found = true + matches = append(matches, k) } } - if found { + if len(matches) == 1 { + res = c.resolvers[matches[0]] + c.logf("Implicitly registering resolver %v for interface type %v", matches[0], typ) + } else if len(matches) > 1 { + p, found := findPreference(c.preferences, typ, key) + if !found { + return nil, &ErrMultipleImplicitInterfaceBindings{Interface: typ, Matches: matches} + } + i := slices.IndexFunc(matches, func(t reflect.Type) bool { + return fullyQualifiedTypeName(t) == p.Implementation + }) + if i == -1 { + return nil, &ErrExplicitBindingNotFound{Preference: p} + } + c.logf("Registering resolver %v for interface type %v by explicit preference", matches[i], typ) + res = c.resolvers[matches[i]] + } + + if len(matches) > 0 { c.resolvers[typ] = res } } @@ -166,7 +182,7 @@ func (c *container) addNode(provider *ProviderDescriptor, key *moduleKey) (inter return nil, fmt.Errorf("one-per-module type %v can't be used as an input parameter", typ) } - vr, err := c.getResolver(typ) + vr, err := c.getResolver(typ, key) if err != nil { return nil, err } @@ -208,7 +224,7 @@ func (c *container) addNode(provider *ProviderDescriptor, key *moduleKey) (inter typ = typ.Elem() } - vr, err := c.getResolver(typ) + vr, err := c.getResolver(typ, key) if err != nil { return nil, err } @@ -323,7 +339,7 @@ func (c *container) resolve(in ProviderInput, moduleKey *moduleKey, caller Locat return reflect.ValueOf(OwnModuleKey{moduleKey}), nil } - vr, err := c.getResolver(in.Type) + vr, err := c.getResolver(in.Type, moduleKey) if err != nil { return reflect.Value{}, err } @@ -433,6 +449,10 @@ func (c container) formatResolveStack() string { return buf.String() } +func (c *container) addPreference(p Preference) { + c.preferences = append(c.preferences, p) +} + func markGraphNodeAsUsed(node *graphviz.Node) { node.SetColor("black") node.SetPenWidth("1.5") diff --git a/depinject/container_test.go b/depinject/container_test.go index d1690ea48256..68c1d297bdca 100644 --- a/depinject/container_test.go +++ b/depinject/container_test.go @@ -653,7 +653,7 @@ type Pond struct { Duck AlsoDuck } -func TestImplicitBindings(t *testing.T) { +func TestImplicitAndExplicitBindings(t *testing.T) { var pond Pond require.NoError(t, @@ -668,13 +668,61 @@ func TestImplicitBindings(t *testing.T) { require.NotNil(t, pond) - require.Error(t, + multImplProvider := depinject.Provide( + func() Mallard { return Mallard{} }, + func() Canvasback { return Canvasback{} }, + func(duck Duck) Pond { + return Pond{Duck: duck} + }) + canvasbackPref := depinject.Prefer( + "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", + "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Canvasback") + + require.ErrorContains(t, depinject.Inject(multImplProvider, &pond), "Multiple implementations") + + require.NoError(t, depinject.Inject( - depinject.Provide( - func() Mallard { return Mallard{} }, - func() Canvasback { return Canvasback{} }, - func(duck Duck) Pond { - return Pond{Duck: duck} - }), + depinject.Configs(canvasbackPref, multImplProvider), &pond)) + + require.IsType(t, pond.Duck, Canvasback{}) + +} + +func TestExplicitModuleBindings(t *testing.T) { + var pond Pond + + require.ErrorContains(t, + depinject.Inject( + depinject.Configs( + depinject.PreferInModule( + "foo", + "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", + "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.DoesNotExist"), + depinject.Provide( + func(depinject.ModuleKey) Mallard { return Mallard{} }, + func(depinject.ModuleKey) Canvasback { return Canvasback{} }), + depinject.ProvideInModule("foo", + func(duck Duck) Pond { + return Pond{Duck: duck} + })), + &pond), "Given the explicit interface binding") + + require.NoError(t, + depinject.Inject( + depinject.Configs( + depinject.PreferInModule( + "foo", + "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", + "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Mallard"), + depinject.Provide( + func(depinject.ModuleKey) Mallard { return Mallard{} }, + func(depinject.ModuleKey) Canvasback { return Canvasback{} }), + depinject.ProvideInModule("foo", + func(duck Duck) Pond { + return Pond{Duck: duck} + })), + &pond)) + + require.IsType(t, pond.Duck, Mallard{}) } diff --git a/depinject/errors.go b/depinject/errors.go index f671f186fdba..d71873a39030 100644 --- a/depinject/errors.go +++ b/depinject/errors.go @@ -8,12 +8,31 @@ import ( ) type ErrMultipleImplicitInterfaceBindings struct { + error Interface reflect.Type - Err error + Matches []reflect.Type } func (err ErrMultipleImplicitInterfaceBindings) Error() string { - return fmt.Sprintf("Multiple implementations found for interface %v", err.Interface) + return fmt.Sprintf("Multiple implementations found for interface %v: %v", err.Interface, err.Matches) +} + +type ErrExplicitBindingNotFound struct { + Preference Preference + Interface reflect.Type + error +} + +func (err ErrExplicitBindingNotFound) Error() string { + p := err.Preference + if p.ModuleName != "" { + return fmt.Sprintf("Given the explicit interface binding %s in module %s, a provider of type %s was not found.", + p.Interface, p.ModuleName, p.Implementation) + } else { + return fmt.Sprintf("Given the explicit interface binding %s, a provider of type %s was not found.", + p.Interface, p.Implementation) + } + } func duplicateDefinitionError(typ reflect.Type, duplicateLoc Location, existingLoc string) error { diff --git a/depinject/preference.go b/depinject/preference.go new file mode 100644 index 000000000000..bd98be708f5f --- /dev/null +++ b/depinject/preference.go @@ -0,0 +1,37 @@ +package depinject + +import ( + "fmt" + "reflect" +) + +// Preference defines a type binding preference to bind Interface to type Implementation when being provided as a +// dependency to the module with ModuleName. If ModuleName is empty then the type binding is applied globally, +// not module-scoped. +type Preference struct { + Interface string + Implementation string + ModuleName string +} + +func fullyQualifiedTypeName(typ reflect.Type) string { + return fmt.Sprintf("%s/%v", typ.PkgPath(), typ) +} + +func findPreference(ps []Preference, typ reflect.Type, key *moduleKey) (Preference, bool) { + if key != nil { + for _, p := range ps { + if p.Interface == fullyQualifiedTypeName(typ) && (key.name == p.ModuleName) { + return p, true + } + } + } + + for _, p := range ps { + if p.Interface == fullyQualifiedTypeName(typ) && p.ModuleName == "" { + return p, true + } + } + + return Preference{}, false +} From 0a586840c593e61d6aec0f40754d26c8f94b6e9b Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 7 Jun 2022 15:12:50 -0500 Subject: [PATCH 09/41] Fill in docstrings --- depinject/config.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/depinject/config.go b/depinject/config.go index 117a5f09d132..59907644ec10 100644 --- a/depinject/config.go +++ b/depinject/config.go @@ -48,14 +48,16 @@ func provide(ctr *container, key *moduleKey, providers []interface{}) error { return nil } -// Prefer defines a container configuration +// Prefer defines a container configuration for an explicit interface binding of inTypeName to outTypeName +// in global scope. func Prefer(inTypeName string, outTypeName string) Config { return containerConfig(func(ctr *container) error { return prefer(ctr, inTypeName, outTypeName, "") }) } -// PreferInModule defines a container configuration +// PreferInModule defines a container configuration for an explicit interface binding of inTypeName to outTypeName +// in the scope of the module with name moduleName. func PreferInModule(moduleName string, inTypeName string, outTypeName string) Config { return containerConfig(func(ctr *container) error { return prefer(ctr, inTypeName, outTypeName, moduleName) From 0468f275184c983a7318bcb29dd57d9c3b2d3c71 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 7 Jun 2022 15:28:19 -0500 Subject: [PATCH 10/41] Do not export preference struct --- depinject/config.go | 2 +- depinject/container.go | 6 +++--- depinject/errors.go | 2 +- depinject/preference.go | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/depinject/config.go b/depinject/config.go index 59907644ec10..c3208ee3c8f1 100644 --- a/depinject/config.go +++ b/depinject/config.go @@ -65,7 +65,7 @@ func PreferInModule(moduleName string, inTypeName string, outTypeName string) Co } func prefer(ctr *container, inTypeName string, outTypeName string, moduleName string) error { - ctr.addPreference(Preference{ + ctr.addPreference(preference{ Interface: inTypeName, Implementation: outTypeName, ModuleName: moduleName, diff --git a/depinject/container.go b/depinject/container.go index 14b5cfec9cc4..4badcd0ad5f3 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -13,7 +13,7 @@ type container struct { *debugConfig resolvers map[reflect.Type]resolver - preferences []Preference + preferences []preference moduleKeys map[string]*moduleKey @@ -32,7 +32,7 @@ func newContainer(cfg *debugConfig) *container { debugConfig: cfg, resolvers: map[reflect.Type]resolver{}, moduleKeys: map[string]*moduleKey{}, - preferences: []Preference{}, + preferences: []preference{}, callerStack: nil, callerMap: map[Location]bool{}, } @@ -449,7 +449,7 @@ func (c container) formatResolveStack() string { return buf.String() } -func (c *container) addPreference(p Preference) { +func (c *container) addPreference(p preference) { c.preferences = append(c.preferences, p) } diff --git a/depinject/errors.go b/depinject/errors.go index d71873a39030..79167d4895c3 100644 --- a/depinject/errors.go +++ b/depinject/errors.go @@ -18,7 +18,7 @@ func (err ErrMultipleImplicitInterfaceBindings) Error() string { } type ErrExplicitBindingNotFound struct { - Preference Preference + Preference preference Interface reflect.Type error } diff --git a/depinject/preference.go b/depinject/preference.go index bd98be708f5f..52c80cf61cf9 100644 --- a/depinject/preference.go +++ b/depinject/preference.go @@ -5,10 +5,10 @@ import ( "reflect" ) -// Preference defines a type binding preference to bind Interface to type Implementation when being provided as a +// preference defines a type binding preference to bind Interface to type Implementation when being provided as a // dependency to the module with ModuleName. If ModuleName is empty then the type binding is applied globally, // not module-scoped. -type Preference struct { +type preference struct { Interface string Implementation string ModuleName string @@ -18,7 +18,7 @@ func fullyQualifiedTypeName(typ reflect.Type) string { return fmt.Sprintf("%s/%v", typ.PkgPath(), typ) } -func findPreference(ps []Preference, typ reflect.Type, key *moduleKey) (Preference, bool) { +func findPreference(ps []preference, typ reflect.Type, key *moduleKey) (preference, bool) { if key != nil { for _, p := range ps { if p.Interface == fullyQualifiedTypeName(typ) && (key.name == p.ModuleName) { @@ -33,5 +33,5 @@ func findPreference(ps []Preference, typ reflect.Type, key *moduleKey) (Preferen } } - return Preference{}, false + return preference{}, false } From 23ea6a7c6e1c029a85f82d565d6ffbd9c6357011 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 7 Jun 2022 15:33:18 -0500 Subject: [PATCH 11/41] Add docstrings for errors --- depinject/errors.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/depinject/errors.go b/depinject/errors.go index 79167d4895c3..f7c6a1f07332 100644 --- a/depinject/errors.go +++ b/depinject/errors.go @@ -7,6 +7,9 @@ import ( "github.com/pkg/errors" ) +// ErrMultipleImplicitInterfaceBindings defines an error condition where an attempt was made to implicitly bind +// Interface to a concrete type, but the container was unable to come to a resolution because multiple Matches +// were found. type ErrMultipleImplicitInterfaceBindings struct { error Interface reflect.Type @@ -17,6 +20,8 @@ func (err ErrMultipleImplicitInterfaceBindings) Error() string { return fmt.Sprintf("Multiple implementations found for interface %v: %v", err.Interface, err.Matches) } +// ErrExplicitBindingNotFound defines an error condition where an explicit binding of Interface type was marked as a +// Preference but no provider for the requested type implementation was found in the container. type ErrExplicitBindingNotFound struct { Preference preference Interface reflect.Type From 295b97c1a508aa8852d9fe0f915761ef90f07e61 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 7 Jun 2022 15:35:08 -0500 Subject: [PATCH 12/41] Rename error --- depinject/container.go | 2 +- depinject/errors.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/depinject/container.go b/depinject/container.go index 4badcd0ad5f3..906d23877670 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -146,7 +146,7 @@ func (c *container) getResolver(typ reflect.Type, key *moduleKey) (resolver, err return fullyQualifiedTypeName(t) == p.Implementation }) if i == -1 { - return nil, &ErrExplicitBindingNotFound{Preference: p} + return nil, &ErrNoTypeForExplicitBindingFound{Preference: p} } c.logf("Registering resolver %v for interface type %v by explicit preference", matches[i], typ) res = c.resolvers[matches[i]] diff --git a/depinject/errors.go b/depinject/errors.go index f7c6a1f07332..db8b0e7606b1 100644 --- a/depinject/errors.go +++ b/depinject/errors.go @@ -20,15 +20,15 @@ func (err ErrMultipleImplicitInterfaceBindings) Error() string { return fmt.Sprintf("Multiple implementations found for interface %v: %v", err.Interface, err.Matches) } -// ErrExplicitBindingNotFound defines an error condition where an explicit binding of Interface type was marked as a +// ErrNoTypeForExplicitBindingFound defines an error condition where an explicit binding of Interface type was marked as a // Preference but no provider for the requested type implementation was found in the container. -type ErrExplicitBindingNotFound struct { +type ErrNoTypeForExplicitBindingFound struct { Preference preference Interface reflect.Type error } -func (err ErrExplicitBindingNotFound) Error() string { +func (err ErrNoTypeForExplicitBindingFound) Error() string { p := err.Preference if p.ModuleName != "" { return fmt.Sprintf("Given the explicit interface binding %s in module %s, a provider of type %s was not found.", From ede3aa8bd58e3e248297abbfa3709ea5f6f88d97 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 7 Jun 2022 16:04:11 -0500 Subject: [PATCH 13/41] Rework state for preferred resolvers --- depinject/container.go | 28 ++++++++++++++++------------ depinject/container_test.go | 20 ++++++++++++++++++++ depinject/preference.go | 7 +++++++ 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/depinject/container.go b/depinject/container.go index 906d23877670..c660810ffb56 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -12,8 +12,9 @@ import ( type container struct { *debugConfig - resolvers map[reflect.Type]resolver - preferences []preference + resolvers map[reflect.Type]resolver + preferences []preference + preferredResolvers map[string]resolver moduleKeys map[string]*moduleKey @@ -29,12 +30,13 @@ type resolveFrame struct { func newContainer(cfg *debugConfig) *container { return &container{ - debugConfig: cfg, - resolvers: map[reflect.Type]resolver{}, - moduleKeys: map[string]*moduleKey{}, - preferences: []preference{}, - callerStack: nil, - callerMap: map[Location]bool{}, + debugConfig: cfg, + resolvers: map[reflect.Type]resolver{}, + moduleKeys: map[string]*moduleKey{}, + preferences: []preference{}, + preferredResolvers: map[string]resolver{}, + callerStack: nil, + callerMap: map[Location]bool{}, } } @@ -79,6 +81,10 @@ func (c *container) call(provider *ProviderDescriptor, moduleKey *moduleKey) ([] func (c *container) getResolver(typ reflect.Type, key *moduleKey) (resolver, error) { c.logf("Resolving %v", typ) + if vr, ok := c.preferredResolvers[fullyQualifiedModuleTypeName(typ, key)]; ok { + return vr, nil + } + if vr, ok := c.resolvers[typ]; ok { return vr, nil } @@ -137,6 +143,7 @@ func (c *container) getResolver(typ reflect.Type, key *moduleKey) (resolver, err if len(matches) == 1 { res = c.resolvers[matches[0]] c.logf("Implicitly registering resolver %v for interface type %v", matches[0], typ) + c.preferredResolvers[fullyQualifiedModuleTypeName(typ, key)] = res } else if len(matches) > 1 { p, found := findPreference(c.preferences, typ, key) if !found { @@ -150,10 +157,7 @@ func (c *container) getResolver(typ reflect.Type, key *moduleKey) (resolver, err } c.logf("Registering resolver %v for interface type %v by explicit preference", matches[i], typ) res = c.resolvers[matches[i]] - } - - if len(matches) > 0 { - c.resolvers[typ] = res + c.preferredResolvers[fullyQualifiedModuleTypeName(typ, key)] = res } } diff --git a/depinject/container_test.go b/depinject/container_test.go index 68c1d297bdca..43ac2da362e7 100644 --- a/depinject/container_test.go +++ b/depinject/container_test.go @@ -725,4 +725,24 @@ func TestExplicitModuleBindings(t *testing.T) { &pond)) require.IsType(t, pond.Duck, Mallard{}) + + require.ErrorContains(t, + depinject.Inject( + depinject.Configs( + depinject.PreferInModule( + "foo", + "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", + "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Mallard"), + depinject.Provide( + func(depinject.ModuleKey) Mallard { return Mallard{} }, + func(depinject.ModuleKey) Canvasback { return Canvasback{} }, + func(duck Duck) Pond { + return Pond{Duck: duck} + }), + depinject.ProvideInModule( + "foo", + func(duck Duck) Pond { + return Pond{Duck: duck} + })), + &pond), "Multiple implementations found for interface") } diff --git a/depinject/preference.go b/depinject/preference.go index 52c80cf61cf9..4af533d74c1e 100644 --- a/depinject/preference.go +++ b/depinject/preference.go @@ -18,6 +18,13 @@ func fullyQualifiedTypeName(typ reflect.Type) string { return fmt.Sprintf("%s/%v", typ.PkgPath(), typ) } +func fullyQualifiedModuleTypeName(typ reflect.Type, key *moduleKey) string { + if key == nil { + return fmt.Sprintf("%s;", fullyQualifiedTypeName(typ)) + } + return fmt.Sprintf("%s;%s", fullyQualifiedTypeName(typ), key.name) +} + func findPreference(ps []preference, typ reflect.Type, key *moduleKey) (preference, bool) { if key != nil { for _, p := range ps { From d7dd9ad2bbf5ddd00dc1169a01e3e4cc830eb83c Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 8 Jun 2022 09:48:10 -0500 Subject: [PATCH 14/41] Refactor preference lookup --- depinject/config.go | 10 +++-- depinject/container.go | 77 +++++++++++++++++++++++-------------- depinject/container_test.go | 19 +++++++++ depinject/errors.go | 26 ++++++++++--- depinject/preference.go | 33 +++++----------- 5 files changed, 105 insertions(+), 60 deletions(-) diff --git a/depinject/config.go b/depinject/config.go index c3208ee3c8f1..f1ec4bbf21e1 100644 --- a/depinject/config.go +++ b/depinject/config.go @@ -65,10 +65,14 @@ func PreferInModule(moduleName string, inTypeName string, outTypeName string) Co } func prefer(ctr *container, inTypeName string, outTypeName string, moduleName string) error { + var mk *moduleKey + if moduleName != "" { + mk = &moduleKey{name: moduleName} + } ctr.addPreference(preference{ - Interface: inTypeName, - Implementation: outTypeName, - ModuleName: moduleName, + interfaceName: inTypeName, + implTypeName: outTypeName, + moduleKey: mk, }) return nil diff --git a/depinject/container.go b/depinject/container.go index c660810ffb56..dc05f9aa8e3c 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -5,16 +5,14 @@ import ( "fmt" "github.com/cosmos/cosmos-sdk/depinject/internal/graphviz" "github.com/pkg/errors" - "golang.org/x/exp/slices" "reflect" ) type container struct { *debugConfig - resolvers map[reflect.Type]resolver - preferences []preference - preferredResolvers map[string]resolver + resolvers map[reflect.Type]resolver + preferences map[string]preference moduleKeys map[string]*moduleKey @@ -30,13 +28,12 @@ type resolveFrame struct { func newContainer(cfg *debugConfig) *container { return &container{ - debugConfig: cfg, - resolvers: map[reflect.Type]resolver{}, - moduleKeys: map[string]*moduleKey{}, - preferences: []preference{}, - preferredResolvers: map[string]resolver{}, - callerStack: nil, - callerMap: map[Location]bool{}, + debugConfig: cfg, + resolvers: map[reflect.Type]resolver{}, + moduleKeys: map[string]*moduleKey{}, + preferences: map[string]preference{}, + callerStack: nil, + callerMap: map[Location]bool{}, } } @@ -81,8 +78,13 @@ func (c *container) call(provider *ProviderDescriptor, moduleKey *moduleKey) ([] func (c *container) getResolver(typ reflect.Type, key *moduleKey) (resolver, error) { c.logf("Resolving %v", typ) - if vr, ok := c.preferredResolvers[fullyQualifiedModuleTypeName(typ, key)]; ok { - return vr, nil + + pr, err := c.getPreferredResolver(typ, key) + if err != nil { + return nil, err + } + if pr != nil { + return pr, nil } if vr, ok := c.resolvers[typ]; ok { @@ -143,27 +145,46 @@ func (c *container) getResolver(typ reflect.Type, key *moduleKey) (resolver, err if len(matches) == 1 { res = c.resolvers[matches[0]] c.logf("Implicitly registering resolver %v for interface type %v", matches[0], typ) - c.preferredResolvers[fullyQualifiedModuleTypeName(typ, key)] = res + c.resolvers[typ] = res } else if len(matches) > 1 { - p, found := findPreference(c.preferences, typ, key) - if !found { - return nil, &ErrMultipleImplicitInterfaceBindings{Interface: typ, Matches: matches} - } - i := slices.IndexFunc(matches, func(t reflect.Type) bool { - return fullyQualifiedTypeName(t) == p.Implementation - }) - if i == -1 { - return nil, &ErrNoTypeForExplicitBindingFound{Preference: p} - } - c.logf("Registering resolver %v for interface type %v by explicit preference", matches[i], typ) - res = c.resolvers[matches[i]] - c.preferredResolvers[fullyQualifiedModuleTypeName(typ, key)] = res + return nil, &ErrMultipleImplicitInterfaceBindings{Interface: typ, Matches: matches} } } return res, nil } +func (c *container) getPreferredResolver(typ reflect.Type, key *moduleKey) (resolver, error) { + var pref preference + var found bool + + // module scoped binding takes precedence + pref, found = c.preferences[preferenceKeyFromType(typ, key)] + + // fallback to global scope binding + if !found { + pref, found = c.preferences[preferenceKeyFromType(typ, nil)] + } + + if !found { + return nil, nil + } + + if pref.resolver != nil { + return pref.resolver, nil + } + + for k, res := range c.resolvers { + if fullyQualifiedTypeName(k) == pref.implTypeName { + c.logf("Registering resolver %v for interface type %v by explicit preference", k, typ) + pref.resolver = res + return res, nil + } + } + + return nil, NewErrNoTypeForExplicitBindingFound(pref) +} + var stringType = reflect.TypeOf("") func (c *container) addNode(provider *ProviderDescriptor, key *moduleKey) (interface{}, error) { @@ -454,7 +475,7 @@ func (c container) formatResolveStack() string { } func (c *container) addPreference(p preference) { - c.preferences = append(c.preferences, p) + c.preferences[preferenceKeyFromTypeName(p.interfaceName, p.moduleKey)] = p } func markGraphNodeAsUsed(node *graphviz.Node) { diff --git a/depinject/container_test.go b/depinject/container_test.go index 43ac2da362e7..bd20e5bed445 100644 --- a/depinject/container_test.go +++ b/depinject/container_test.go @@ -708,6 +708,7 @@ func TestExplicitModuleBindings(t *testing.T) { })), &pond), "Given the explicit interface binding") + // naive module-scope binding test require.NoError(t, depinject.Inject( depinject.Configs( @@ -726,6 +727,7 @@ func TestExplicitModuleBindings(t *testing.T) { require.IsType(t, pond.Duck, Mallard{}) + // module-scoped explicit binding does not interfere with global scope bindings. require.ErrorContains(t, depinject.Inject( depinject.Configs( @@ -745,4 +747,21 @@ func TestExplicitModuleBindings(t *testing.T) { return Pond{Duck: duck} })), &pond), "Multiple implementations found for interface") + + // default to global binding if no module scoped binding was found + require.NoError(t, + depinject.Inject( + depinject.Configs( + depinject.Prefer( + "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", + "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Mallard"), + depinject.Provide( + func(depinject.ModuleKey) Mallard { return Mallard{} }, + func(depinject.ModuleKey) Canvasback { return Canvasback{} }), + depinject.ProvideInModule( + "foo", + func(duck Duck) Pond { + return Pond{Duck: duck} + })), + &pond), "Multiple implementations found for interface") } diff --git a/depinject/errors.go b/depinject/errors.go index db8b0e7606b1..52a9b5c7f25f 100644 --- a/depinject/errors.go +++ b/depinject/errors.go @@ -23,19 +23,33 @@ func (err ErrMultipleImplicitInterfaceBindings) Error() string { // ErrNoTypeForExplicitBindingFound defines an error condition where an explicit binding of Interface type was marked as a // Preference but no provider for the requested type implementation was found in the container. type ErrNoTypeForExplicitBindingFound struct { - Preference preference - Interface reflect.Type + PreferredType string + PreferredInterface string + ModuleName string + Interface reflect.Type error } +func NewErrNoTypeForExplicitBindingFound(p preference) ErrNoTypeForExplicitBindingFound { + var moduleName string + if p.moduleKey != nil { + moduleName = p.moduleKey.name + } + + return ErrNoTypeForExplicitBindingFound{ + PreferredType: p.implTypeName, + PreferredInterface: p.interfaceName, + ModuleName: moduleName, + } +} + func (err ErrNoTypeForExplicitBindingFound) Error() string { - p := err.Preference - if p.ModuleName != "" { + if err.ModuleName != "" { return fmt.Sprintf("Given the explicit interface binding %s in module %s, a provider of type %s was not found.", - p.Interface, p.ModuleName, p.Implementation) + err.PreferredInterface, err.ModuleName, err.PreferredType) } else { return fmt.Sprintf("Given the explicit interface binding %s, a provider of type %s was not found.", - p.Interface, p.Implementation) + err.PreferredInterface, err.PreferredType) } } diff --git a/depinject/preference.go b/depinject/preference.go index 4af533d74c1e..12ce6e218fc3 100644 --- a/depinject/preference.go +++ b/depinject/preference.go @@ -5,40 +5,27 @@ import ( "reflect" ) -// preference defines a type binding preference to bind Interface to type Implementation when being provided as a +// preference defines a type binding preference to bind Interface to type implTypeName when being provided as a // dependency to the module with ModuleName. If ModuleName is empty then the type binding is applied globally, // not module-scoped. type preference struct { - Interface string - Implementation string - ModuleName string + interfaceName string + implTypeName string + moduleKey *moduleKey + resolver resolver } func fullyQualifiedTypeName(typ reflect.Type) string { return fmt.Sprintf("%s/%v", typ.PkgPath(), typ) } -func fullyQualifiedModuleTypeName(typ reflect.Type, key *moduleKey) string { +func preferenceKeyFromTypeName(typeName string, key *moduleKey) string { if key == nil { - return fmt.Sprintf("%s;", fullyQualifiedTypeName(typ)) + return fmt.Sprintf("%s;", typeName) } - return fmt.Sprintf("%s;%s", fullyQualifiedTypeName(typ), key.name) + return fmt.Sprintf("%s;%s", typeName, key.name) } -func findPreference(ps []preference, typ reflect.Type, key *moduleKey) (preference, bool) { - if key != nil { - for _, p := range ps { - if p.Interface == fullyQualifiedTypeName(typ) && (key.name == p.ModuleName) { - return p, true - } - } - } - - for _, p := range ps { - if p.Interface == fullyQualifiedTypeName(typ) && p.ModuleName == "" { - return p, true - } - } - - return preference{}, false +func preferenceKeyFromType(typ reflect.Type, key *moduleKey) string { + return preferenceKeyFromTypeName(fullyQualifiedTypeName(typ), key) } From 4986b0fe5fca752aca72431dd000c977d8363abe Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 8 Jun 2022 09:54:43 -0500 Subject: [PATCH 15/41] Clean up docstrings --- depinject/errors.go | 5 ++--- depinject/preference.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/depinject/errors.go b/depinject/errors.go index 52a9b5c7f25f..93f34caa9d8b 100644 --- a/depinject/errors.go +++ b/depinject/errors.go @@ -20,13 +20,12 @@ func (err ErrMultipleImplicitInterfaceBindings) Error() string { return fmt.Sprintf("Multiple implementations found for interface %v: %v", err.Interface, err.Matches) } -// ErrNoTypeForExplicitBindingFound defines an error condition where an explicit binding of Interface type was marked as a -// Preference but no provider for the requested type implementation was found in the container. +// ErrNoTypeForExplicitBindingFound defines an error condition where an explicit binding of PreferredType was marked as +// preference for PreferredInterface, but no provider for the requested PreferredType was found in the container. type ErrNoTypeForExplicitBindingFound struct { PreferredType string PreferredInterface string ModuleName string - Interface reflect.Type error } diff --git a/depinject/preference.go b/depinject/preference.go index 12ce6e218fc3..4bae915252a3 100644 --- a/depinject/preference.go +++ b/depinject/preference.go @@ -5,7 +5,7 @@ import ( "reflect" ) -// preference defines a type binding preference to bind Interface to type implTypeName when being provided as a +// preference defines a type binding preference to bind interfaceName to type implTypeName when being provided as a // dependency to the module with ModuleName. If ModuleName is empty then the type binding is applied globally, // not module-scoped. type preference struct { From b3eeb16da41ba80e47a4924732f79c36ec0a3b53 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 8 Jun 2022 09:55:47 -0500 Subject: [PATCH 16/41] Clean up docstrings --- depinject/preference.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depinject/preference.go b/depinject/preference.go index 4bae915252a3..f5160330e1d3 100644 --- a/depinject/preference.go +++ b/depinject/preference.go @@ -6,7 +6,7 @@ import ( ) // preference defines a type binding preference to bind interfaceName to type implTypeName when being provided as a -// dependency to the module with ModuleName. If ModuleName is empty then the type binding is applied globally, +// dependency to the module identified by moduleKey. If moduleKey is nil then the type binding is applied globally, // not module-scoped. type preference struct { interfaceName string From 6f2ea2d60acd057913d80887351bf8e44dae8c9a Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 8 Jun 2022 13:54:47 -0500 Subject: [PATCH 17/41] Refactor usages of c.resolvers --- depinject/container.go | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/depinject/container.go b/depinject/container.go index dc05f9aa8e3c..fb9a6498dfeb 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -87,7 +87,7 @@ func (c *container) getResolver(typ reflect.Type, key *moduleKey) (resolver, err return pr, nil } - if vr, ok := c.resolvers[typ]; ok { + if vr, ok := c.resolverByType(typ); ok { return vr, nil } @@ -111,8 +111,8 @@ func (c *container) getResolver(typ reflect.Type, key *moduleKey) (resolver, err graphNode: typeGraphNode, } - c.resolvers[elemType] = r - c.resolvers[sliceType] = &sliceGroupResolver{r} + c.addResolver(elemType, r) + c.addResolver(sliceType, &sliceGroupResolver{r}) } else if isOnePerModuleType(elemType) { c.logf("Registering resolver for one-per-module type %v", elemType) mapType := reflect.MapOf(stringType, elemType) @@ -128,11 +128,11 @@ func (c *container) getResolver(typ reflect.Type, key *moduleKey) (resolver, err graphNode: typeGraphNode, } - c.resolvers[elemType] = r - c.resolvers[mapType] = &mapOfOnePerModuleResolver{r} + c.addResolver(elemType, r) + c.addResolver(mapType, &mapOfOnePerModuleResolver{r}) } - res := c.resolvers[typ] + res, _ := c.resolverByType(typ) if res == nil && typ.Kind() == reflect.Interface { var matches []reflect.Type @@ -145,7 +145,7 @@ func (c *container) getResolver(typ reflect.Type, key *moduleKey) (resolver, err if len(matches) == 1 { res = c.resolvers[matches[0]] c.logf("Implicitly registering resolver %v for interface type %v", matches[0], typ) - c.resolvers[typ] = res + c.addResolver(typ, res) } else if len(matches) > 1 { return nil, &ErrMultipleImplicitInterfaceBindings{Interface: typ, Matches: matches} } @@ -270,7 +270,7 @@ func (c *container) addNode(provider *ProviderDescriptor, key *moduleKey) (inter graphNode: typeGraphNode, idxInValues: i, } - c.resolvers[typ] = vr + c.addResolver(typ, vr) } c.addGraphEdge(providerGraphNode, vr.typeGraphNode()) @@ -298,20 +298,20 @@ func (c *container) addNode(provider *ProviderDescriptor, key *moduleKey) (inter c.logf("Registering resolver for module-scoped type %v", typ) - existing, ok := c.resolvers[typ] + existing, ok := c.resolverByType(typ) if ok { return nil, errors.Errorf("duplicate provision of type %v by module-scoped provider %s\n\talready provided by %s", typ, provider.Location, existing.describeLocation()) } typeGraphNode := c.typeGraphNode(typ) - c.resolvers[typ] = &moduleDepResolver{ + c.addResolver(typ, &moduleDepResolver{ typ: typ, idxInValues: i, node: node, valueMap: map[*moduleKey]reflect.Value{}, graphNode: typeGraphNode, - } + }) c.addGraphEdge(providerGraphNode, typeGraphNode) } @@ -327,16 +327,16 @@ func (c *container) supply(value reflect.Value, location Location) error { typeGraphNode := c.typeGraphNode(typ) c.addGraphEdge(locGrapNode, typeGraphNode) - if existing, ok := c.resolvers[typ]; ok { + if existing, ok := c.resolverByType(typ); ok { return duplicateDefinitionError(typ, location, existing.describeLocation()) } - c.resolvers[typ] = &supplyResolver{ + c.addResolver(typ, &supplyResolver{ typ: typ, value: value, loc: location, graphNode: typeGraphNode, - } + }) return nil } @@ -478,6 +478,15 @@ func (c *container) addPreference(p preference) { c.preferences[preferenceKeyFromTypeName(p.interfaceName, p.moduleKey)] = p } +func (c *container) addResolver(typ reflect.Type, r resolver) { + c.resolvers[typ] = r +} + +func (c *container) resolverByType(typ reflect.Type) (resolver, bool) { + res, found := c.resolvers[typ] + return res, found +} + func markGraphNodeAsUsed(node *graphviz.Node) { node.SetColor("black") node.SetPenWidth("1.5") From 0f683f42b6b2a8ed3fb2cbe590d127d7cc346617 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 8 Jun 2022 14:03:53 -0500 Subject: [PATCH 18/41] All is passing with 2 resolver indices --- depinject/container.go | 44 ++++++++++++++++++++++++----------------- depinject/preference.go | 6 +++++- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/depinject/container.go b/depinject/container.go index fb9a6498dfeb..97fd34bf5ad9 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -11,8 +11,9 @@ import ( type container struct { *debugConfig - resolvers map[reflect.Type]resolver - preferences map[string]preference + resolversByType map[reflect.Type]resolver + resolvers map[string]resolver + preferences map[string]preference moduleKeys map[string]*moduleKey @@ -28,12 +29,13 @@ type resolveFrame struct { func newContainer(cfg *debugConfig) *container { return &container{ - debugConfig: cfg, - resolvers: map[reflect.Type]resolver{}, - moduleKeys: map[string]*moduleKey{}, - preferences: map[string]preference{}, - callerStack: nil, - callerMap: map[Location]bool{}, + debugConfig: cfg, + resolversByType: map[reflect.Type]resolver{}, + resolvers: map[string]resolver{}, + moduleKeys: map[string]*moduleKey{}, + preferences: map[string]preference{}, + callerStack: nil, + callerMap: map[Location]bool{}, } } @@ -136,14 +138,14 @@ func (c *container) getResolver(typ reflect.Type, key *moduleKey) (resolver, err if res == nil && typ.Kind() == reflect.Interface { var matches []reflect.Type - for k := range c.resolvers { + for k := range c.resolversByType { if k.Kind() != reflect.Interface && k.Implements(typ) { matches = append(matches, k) } } if len(matches) == 1 { - res = c.resolvers[matches[0]] + res = c.resolversByType[matches[0]] c.logf("Implicitly registering resolver %v for interface type %v", matches[0], typ) c.addResolver(typ, res) } else if len(matches) > 1 { @@ -174,12 +176,12 @@ func (c *container) getPreferredResolver(typ reflect.Type, key *moduleKey) (reso return pref.resolver, nil } - for k, res := range c.resolvers { - if fullyQualifiedTypeName(k) == pref.implTypeName { - c.logf("Registering resolver %v for interface type %v by explicit preference", k, typ) - pref.resolver = res - return res, nil - } + res, ok := c.resolverByTypeName(pref.implTypeName) + if ok { + //c.logf("Registering resolver %v for interface type %v by explicit preference", res.getType(), typ) + pref.resolver = res + return res, nil + } return nil, NewErrNoTypeForExplicitBindingFound(pref) @@ -479,11 +481,17 @@ func (c *container) addPreference(p preference) { } func (c *container) addResolver(typ reflect.Type, r resolver) { - c.resolvers[typ] = r + c.resolversByType[typ] = r + c.resolvers[fullyQualifiedTypeName(typ)] = r } func (c *container) resolverByType(typ reflect.Type) (resolver, bool) { - res, found := c.resolvers[typ] + res, found := c.resolversByType[typ] + return res, found +} + +func (c *container) resolverByTypeName(typeName string) (resolver, bool) { + res, found := c.resolvers[typeName] return res, found } diff --git a/depinject/preference.go b/depinject/preference.go index f5160330e1d3..77a27fc4aa78 100644 --- a/depinject/preference.go +++ b/depinject/preference.go @@ -16,7 +16,11 @@ type preference struct { } func fullyQualifiedTypeName(typ reflect.Type) string { - return fmt.Sprintf("%s/%v", typ.PkgPath(), typ) + pkgType := typ + if typ.Kind() == reflect.Pointer || typ.Kind() == reflect.Slice || typ.Kind() == reflect.Map || typ.Kind() == reflect.Array { + pkgType = typ.Elem() + } + return fmt.Sprintf("%s/%v", pkgType.PkgPath(), typ) } func preferenceKeyFromTypeName(typeName string, key *moduleKey) string { From 0487894009ba36f51c3260fd9a6193ae9cc30783 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 8 Jun 2022 14:09:31 -0500 Subject: [PATCH 19/41] Remove all but 1 usage of resolversByType --- depinject/container.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/depinject/container.go b/depinject/container.go index 97fd34bf5ad9..734982542b8b 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -481,13 +481,14 @@ func (c *container) addPreference(p preference) { } func (c *container) addResolver(typ reflect.Type, r resolver) { - c.resolversByType[typ] = r + //c.resolversByType[typ] = r c.resolvers[fullyQualifiedTypeName(typ)] = r } func (c *container) resolverByType(typ reflect.Type) (resolver, bool) { - res, found := c.resolversByType[typ] - return res, found + //res, found := c.resolversByType[typ] + //return res, found + return c.resolverByTypeName(fullyQualifiedTypeName(typ)) } func (c *container) resolverByTypeName(typeName string) (resolver, bool) { From a967121d17d8a0aab014bd11180c1c7703d5ff96 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 8 Jun 2022 14:12:44 -0500 Subject: [PATCH 20/41] Better docstrings --- depinject/config.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/depinject/config.go b/depinject/config.go index f1ec4bbf21e1..dcc331fb3cda 100644 --- a/depinject/config.go +++ b/depinject/config.go @@ -49,7 +49,14 @@ func provide(ctr *container, key *moduleKey, providers []interface{}) error { } // Prefer defines a container configuration for an explicit interface binding of inTypeName to outTypeName -// in global scope. +// in global scope, for example, +// +// Prefer( +// "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", +// "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Canvasback") +// +// configures the container to *always* provide a Canvasback instance when an input of interface type Duck is +// requested as an input. func Prefer(inTypeName string, outTypeName string) Config { return containerConfig(func(ctr *container) error { return prefer(ctr, inTypeName, outTypeName, "") @@ -57,7 +64,15 @@ func Prefer(inTypeName string, outTypeName string) Config { } // PreferInModule defines a container configuration for an explicit interface binding of inTypeName to outTypeName -// in the scope of the module with name moduleName. +// in the scope of the module with name moduleName. For example, given the configuration +// +// PreferInModule( +// "moduleFoo", +// "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", +// "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Canvasback") +// +// where Duck is an interface and Canvasback implements Duck, the container will attempt to provide a Canvasback +// instance where Duck is requested as an input, but only within the scope of module "moduleFoo". func PreferInModule(moduleName string, inTypeName string, outTypeName string) Config { return containerConfig(func(ctr *container) error { return prefer(ctr, inTypeName, outTypeName, moduleName) From 5f4321b40c0bba5dcc71634234cc34362db694fa Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 8 Jun 2022 14:18:57 -0500 Subject: [PATCH 21/41] Main two indices (for now) --- depinject/container.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/depinject/container.go b/depinject/container.go index 734982542b8b..40d045b28ea9 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -481,13 +481,11 @@ func (c *container) addPreference(p preference) { } func (c *container) addResolver(typ reflect.Type, r resolver) { - //c.resolversByType[typ] = r + c.resolversByType[typ] = r c.resolvers[fullyQualifiedTypeName(typ)] = r } func (c *container) resolverByType(typ reflect.Type) (resolver, bool) { - //res, found := c.resolversByType[typ] - //return res, found return c.resolverByTypeName(fullyQualifiedTypeName(typ)) } From 05bbeb7e8264b85e4dd8a557201385ab39141153 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 8 Jun 2022 14:40:34 -0500 Subject: [PATCH 22/41] Remove 2nd resolver index --- depinject/container.go | 35 +++++++++++++++++------------------ depinject/errors.go | 14 +++++++++++++- depinject/group.go | 4 ++++ depinject/module_dep.go | 4 ++++ depinject/one_per_module.go | 4 ++++ depinject/resolver.go | 1 + depinject/simple.go | 4 ++++ depinject/supply.go | 4 ++++ 8 files changed, 51 insertions(+), 19 deletions(-) diff --git a/depinject/container.go b/depinject/container.go index 40d045b28ea9..5ac03acd2ad7 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -11,9 +11,8 @@ import ( type container struct { *debugConfig - resolversByType map[reflect.Type]resolver - resolvers map[string]resolver - preferences map[string]preference + resolvers map[string]resolver + preferences map[string]preference moduleKeys map[string]*moduleKey @@ -29,13 +28,12 @@ type resolveFrame struct { func newContainer(cfg *debugConfig) *container { return &container{ - debugConfig: cfg, - resolversByType: map[reflect.Type]resolver{}, - resolvers: map[string]resolver{}, - moduleKeys: map[string]*moduleKey{}, - preferences: map[string]preference{}, - callerStack: nil, - callerMap: map[Location]bool{}, + debugConfig: cfg, + resolvers: map[string]resolver{}, + moduleKeys: map[string]*moduleKey{}, + preferences: map[string]preference{}, + callerStack: nil, + callerMap: map[Location]bool{}, } } @@ -137,19 +135,21 @@ func (c *container) getResolver(typ reflect.Type, key *moduleKey) (resolver, err res, _ := c.resolverByType(typ) if res == nil && typ.Kind() == reflect.Interface { - var matches []reflect.Type - for k := range c.resolversByType { - if k.Kind() != reflect.Interface && k.Implements(typ) { - matches = append(matches, k) + matches := map[reflect.Type]reflect.Type{} + var resolverType reflect.Type + for _, r := range c.resolvers { + if r.getType().Kind() != reflect.Interface && r.getType().Implements(typ) { + resolverType = r.getType() + matches[resolverType] = resolverType } } if len(matches) == 1 { - res = c.resolversByType[matches[0]] - c.logf("Implicitly registering resolver %v for interface type %v", matches[0], typ) + res, _ = c.resolverByType(resolverType) + c.logf("Implicitly registering resolver %v for interface type %v", resolverType, typ) c.addResolver(typ, res) } else if len(matches) > 1 { - return nil, &ErrMultipleImplicitInterfaceBindings{Interface: typ, Matches: matches} + return nil, newErrMultipleImplicitInterfaceBindings(typ, matches) } } @@ -481,7 +481,6 @@ func (c *container) addPreference(p preference) { } func (c *container) addResolver(typ reflect.Type, r resolver) { - c.resolversByType[typ] = r c.resolvers[fullyQualifiedTypeName(typ)] = r } diff --git a/depinject/errors.go b/depinject/errors.go index 93f34caa9d8b..81420efbd751 100644 --- a/depinject/errors.go +++ b/depinject/errors.go @@ -16,8 +16,20 @@ type ErrMultipleImplicitInterfaceBindings struct { Matches []reflect.Type } +func newErrMultipleImplicitInterfaceBindings(i reflect.Type, matches map[reflect.Type]reflect.Type) ErrMultipleImplicitInterfaceBindings { + var ms []reflect.Type + for k := range matches { + ms = append(ms, k) + } + return ErrMultipleImplicitInterfaceBindings{Interface: i, Matches: ms} +} + func (err ErrMultipleImplicitInterfaceBindings) Error() string { - return fmt.Sprintf("Multiple implementations found for interface %v: %v", err.Interface, err.Matches) + matchesStr := "" + for _, m := range err.Matches { + matchesStr = fmt.Sprintf("%s %s", matchesStr, fullyQualifiedTypeName(m)) + } + return fmt.Sprintf("Multiple implementations found for interface %v: %v", err.Interface, matchesStr) } // ErrNoTypeForExplicitBindingFound defines an error condition where an explicit binding of PreferredType was marked as diff --git a/depinject/group.go b/depinject/group.go index f966a739b5bb..4656b1c29ba6 100644 --- a/depinject/group.go +++ b/depinject/group.go @@ -38,6 +38,10 @@ type groupResolver struct { graphNode *graphviz.Node } +func (g *groupResolver) getType() reflect.Type { + return g.sliceType +} + type sliceGroupResolver struct { *groupResolver } diff --git a/depinject/module_dep.go b/depinject/module_dep.go index 0a9d8b820a7d..643cb5563063 100644 --- a/depinject/module_dep.go +++ b/depinject/module_dep.go @@ -20,6 +20,10 @@ type moduleDepResolver struct { graphNode *graphviz.Node } +func (s moduleDepResolver) getType() reflect.Type { + return s.typ +} + func (s moduleDepResolver) describeLocation() string { return s.node.provider.Location.String() } diff --git a/depinject/one_per_module.go b/depinject/one_per_module.go index 498ec1a6021b..26ce1f0c3974 100644 --- a/depinject/one_per_module.go +++ b/depinject/one_per_module.go @@ -37,6 +37,10 @@ type onePerModuleResolver struct { graphNode *graphviz.Node } +func (o *onePerModuleResolver) getType() reflect.Type { + return o.mapType +} + type mapOfOnePerModuleResolver struct { *onePerModuleResolver } diff --git a/depinject/resolver.go b/depinject/resolver.go index a38098e986c3..9355df3c1733 100644 --- a/depinject/resolver.go +++ b/depinject/resolver.go @@ -11,4 +11,5 @@ type resolver interface { resolve(*container, *moduleKey, Location) (reflect.Value, error) describeLocation() string typeGraphNode() *graphviz.Node + getType() reflect.Type } diff --git a/depinject/simple.go b/depinject/simple.go index 5857b555e925..73c29e0bb746 100644 --- a/depinject/simple.go +++ b/depinject/simple.go @@ -22,6 +22,10 @@ type simpleResolver struct { graphNode *graphviz.Node } +func (s *simpleResolver) getType() reflect.Type { + return s.typ +} + func (s *simpleResolver) describeLocation() string { return s.node.provider.Location.String() } diff --git a/depinject/supply.go b/depinject/supply.go index fac6156444e1..0be0caef1920 100644 --- a/depinject/supply.go +++ b/depinject/supply.go @@ -13,6 +13,10 @@ type supplyResolver struct { graphNode *graphviz.Node } +func (s supplyResolver) getType() reflect.Type { + return s.typ +} + func (s supplyResolver) describeLocation() string { return s.loc.String() } From fe1356c6cfd40fd0a96f149c3e53bc529f438ef8 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 8 Jun 2022 15:02:19 -0500 Subject: [PATCH 23/41] Don't export error constructor fn --- depinject/container.go | 2 +- depinject/errors.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depinject/container.go b/depinject/container.go index 5ac03acd2ad7..1eb25647dd0c 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -184,7 +184,7 @@ func (c *container) getPreferredResolver(typ reflect.Type, key *moduleKey) (reso } - return nil, NewErrNoTypeForExplicitBindingFound(pref) + return nil, newErrNoTypeForExplicitBindingFound(pref) } var stringType = reflect.TypeOf("") diff --git a/depinject/errors.go b/depinject/errors.go index 81420efbd751..4d2e0dc21a57 100644 --- a/depinject/errors.go +++ b/depinject/errors.go @@ -41,7 +41,7 @@ type ErrNoTypeForExplicitBindingFound struct { error } -func NewErrNoTypeForExplicitBindingFound(p preference) ErrNoTypeForExplicitBindingFound { +func newErrNoTypeForExplicitBindingFound(p preference) ErrNoTypeForExplicitBindingFound { var moduleName string if p.moduleKey != nil { moduleName = p.moduleKey.name From 445a410b6f12bda2ab35cc91d68b8dccc5052f54 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 8 Jun 2022 23:10:23 -0500 Subject: [PATCH 24/41] Update docstring Co-authored-by: Aaron Craelius --- depinject/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depinject/config.go b/depinject/config.go index dcc331fb3cda..defc11e170f5 100644 --- a/depinject/config.go +++ b/depinject/config.go @@ -72,7 +72,7 @@ func Prefer(inTypeName string, outTypeName string) Config { // "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Canvasback") // // where Duck is an interface and Canvasback implements Duck, the container will attempt to provide a Canvasback -// instance where Duck is requested as an input, but only within the scope of module "moduleFoo". +// instance where Duck is requested as an input from module "moduleFoo". func PreferInModule(moduleName string, inTypeName string, outTypeName string) Config { return containerConfig(func(ctr *container) error { return prefer(ctr, inTypeName, outTypeName, moduleName) From e6735f9803d9a4d16574c92079a51036a6efc17b Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 8 Jun 2022 23:11:46 -0500 Subject: [PATCH 25/41] Don't ignore bool in resolverByType call --- depinject/container.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/depinject/container.go b/depinject/container.go index 1eb25647dd0c..16423cc4a4f3 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -132,9 +132,9 @@ func (c *container) getResolver(typ reflect.Type, key *moduleKey) (resolver, err c.addResolver(mapType, &mapOfOnePerModuleResolver{r}) } - res, _ := c.resolverByType(typ) + res, found := c.resolverByType(typ) - if res == nil && typ.Kind() == reflect.Interface { + if !found && typ.Kind() == reflect.Interface { matches := map[reflect.Type]reflect.Type{} var resolverType reflect.Type for _, r := range c.resolvers { From e57eedf77b69e8d14dcfc57d33eae0dfc423b558 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 8 Jun 2022 23:13:46 -0500 Subject: [PATCH 26/41] Remove unnecessary params to provider fn in test --- depinject/container_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/depinject/container_test.go b/depinject/container_test.go index bd20e5bed445..035ebb07a918 100644 --- a/depinject/container_test.go +++ b/depinject/container_test.go @@ -700,8 +700,8 @@ func TestExplicitModuleBindings(t *testing.T) { "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.DoesNotExist"), depinject.Provide( - func(depinject.ModuleKey) Mallard { return Mallard{} }, - func(depinject.ModuleKey) Canvasback { return Canvasback{} }), + func() Mallard { return Mallard{} }, + func() Canvasback { return Canvasback{} }), depinject.ProvideInModule("foo", func(duck Duck) Pond { return Pond{Duck: duck} From 5ef8b4170270f00121c9c46c8cec527c6ea47d59 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 8 Jun 2022 23:17:17 -0500 Subject: [PATCH 27/41] Fill in comment in test case --- depinject/container_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/depinject/container_test.go b/depinject/container_test.go index 035ebb07a918..60dffc769a40 100644 --- a/depinject/container_test.go +++ b/depinject/container_test.go @@ -728,6 +728,8 @@ func TestExplicitModuleBindings(t *testing.T) { require.IsType(t, pond.Duck, Mallard{}) // module-scoped explicit binding does not interfere with global scope bindings. + // PreferInModule should result in successful resolution of Duck in module-scope, but in global scope there should + // be a MultipleImplementations error. require.ErrorContains(t, depinject.Inject( depinject.Configs( From 6e7d5926fef1e5ae7e7e2ae8804d42cceb655554 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 8 Jun 2022 23:18:16 -0500 Subject: [PATCH 28/41] Rename field in error struct --- depinject/errors.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/depinject/errors.go b/depinject/errors.go index 4d2e0dc21a57..f892a8cc798e 100644 --- a/depinject/errors.go +++ b/depinject/errors.go @@ -33,11 +33,11 @@ func (err ErrMultipleImplicitInterfaceBindings) Error() string { } // ErrNoTypeForExplicitBindingFound defines an error condition where an explicit binding of PreferredType was marked as -// preference for PreferredInterface, but no provider for the requested PreferredType was found in the container. +// a preference for Interface, but no provider for the requested PreferredType was found in the container. type ErrNoTypeForExplicitBindingFound struct { - PreferredType string - PreferredInterface string - ModuleName string + PreferredType string + Interface string + ModuleName string error } @@ -48,19 +48,19 @@ func newErrNoTypeForExplicitBindingFound(p preference) ErrNoTypeForExplicitBindi } return ErrNoTypeForExplicitBindingFound{ - PreferredType: p.implTypeName, - PreferredInterface: p.interfaceName, - ModuleName: moduleName, + PreferredType: p.implTypeName, + Interface: p.interfaceName, + ModuleName: moduleName, } } func (err ErrNoTypeForExplicitBindingFound) Error() string { if err.ModuleName != "" { return fmt.Sprintf("Given the explicit interface binding %s in module %s, a provider of type %s was not found.", - err.PreferredInterface, err.ModuleName, err.PreferredType) + err.Interface, err.ModuleName, err.PreferredType) } else { return fmt.Sprintf("Given the explicit interface binding %s, a provider of type %s was not found.", - err.PreferredInterface, err.PreferredType) + err.Interface, err.PreferredType) } } From de343d458aa68c19630177807d6f0e2e6deaf7a9 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 9 Jun 2022 10:03:15 -0500 Subject: [PATCH 29/41] Improve multiple impl error and fix a failing test in core --- core/internal/testpb/modules.go | 6 +++++- depinject/errors.go | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/core/internal/testpb/modules.go b/core/internal/testpb/modules.go index 944a1ba75f55..358f4b90e5ed 100644 --- a/core/internal/testpb/modules.go +++ b/core/internal/testpb/modules.go @@ -95,4 +95,8 @@ type keeperB struct { a KeeperA } -type KeeperB interface{} +type KeeperB interface { + isKeeperB() +} + +func (k keeperB) isKeeperB() {} diff --git a/depinject/errors.go b/depinject/errors.go index f892a8cc798e..7350d661f0fc 100644 --- a/depinject/errors.go +++ b/depinject/errors.go @@ -27,9 +27,9 @@ func newErrMultipleImplicitInterfaceBindings(i reflect.Type, matches map[reflect func (err ErrMultipleImplicitInterfaceBindings) Error() string { matchesStr := "" for _, m := range err.Matches { - matchesStr = fmt.Sprintf("%s %s", matchesStr, fullyQualifiedTypeName(m)) + matchesStr = fmt.Sprintf("%s\n %s", matchesStr, fullyQualifiedTypeName(m)) } - return fmt.Sprintf("Multiple implementations found for interface %v: %v", err.Interface, matchesStr) + return fmt.Sprintf("Multiple implementations found for interface %v: %s", err.Interface, matchesStr) } // ErrNoTypeForExplicitBindingFound defines an error condition where an explicit binding of PreferredType was marked as From 9089afb2181bbd884df5eed0e48100dd2a63927c Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 9 Jun 2022 11:27:43 -0500 Subject: [PATCH 30/41] First pass on interface type resolution docs --- depinject/README.md | 101 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/depinject/README.md b/depinject/README.md index c234bead99de..62aefa3a9c72 100644 --- a/depinject/README.md +++ b/depinject/README.md @@ -5,9 +5,17 @@ `depinject` is a dependency injection framework for the Cosmos SDK. This module together with `core/appconfig` are meant to simplify the definition of a blockchain by replacing most of app.go's boilerplate code with a configuration file (YAML or JSON). +Configuration is provided by a single +[YAML file](https://github.com/cosmos/cosmos-sdk/blob/main/simapp/app.yaml) as specified by +[proto3](https://github.com/cosmos/cosmos-sdk/blob/main/proto/cosmos/auth/module/v1/module.proto) message types. This +configuration is often passed to [provider functions](https://github.com/cosmos/cosmos-sdk/blob/33c4bac3d3acbb820b97173882fa9feddacb2f5a/runtime/module.go#L106-L131) +whose outputs form the concrete dependency tree. Type bindings and tree shape are resolved by reflecting over provider +functions, identifying function parameters as inputs (dependencies) and return values as outputs (tree nodes). + ## Usage -### `depinject` example +`depinject` includes an expressive and composable [Configuration API](https://pkg.go.dev/github.com/cosmos/cosmos-sdk/depinject#Config). +A core configuration is `Provide`, for example this code snippet ```go package main @@ -15,7 +23,7 @@ package main import ( "fmt" - "cosmossdk.io/depinject" + "github.com/cosmos/cosmos-sdk/depinject" ) type AnotherInt int @@ -39,10 +47,97 @@ func main() { } ``` +demonstrates the registration of free **provider functions** via the `Provide` API. Provider functions form the basis of the +dependency tree, they are introspected then their inputs identified as dependencies and outputs as dependants, either for +another provider function or state stored outside the DI container, as is the case of `&x` and `&y` above. + +### Interface type resolution + +`depinject` supports interface types as inputs to provider functions. In the SDK's case this pattern is used to decouple +`Keeper` dependencies between modules. For example `x/bank` expects an [AccountKeeper] interface as [input to provideModule](https://github.com/cosmos/cosmos-sdk/blob/de343d458aa68c19630177807d6f0e2e6deaf7a9/x/bank/module.go#L224). +Concretely `SimApp` uses the implementation in `x/auth`, but this design allows for this loose coupling to change. + +Given the following types + +```golang +package duck + +type Duck interface { + quack() +} + +type AlsoDuck interface { + quack() +} + +type Mallard struct{} +type Canvasback struct{} + +func (duck Mallard) quack() {} +func (duck Canvasback) quack() {} + +type Pond struct { + Duck AlsoDuck +} +``` + +This usage + +```golang +var pond Pond + +depinject.Inject( + depinject.Provide( + func() Mallard { return Mallard{} }, + func(duck Duck) Pond { + return Pond{Duck: duck} + }), + &pond) +``` + +results in an *implicit* binding of `Duck` to `Mallard`. This works because there is only one implementation of `Duck` +in the container. However, adding a second provider of `Duck` will result in an error: + +```golang +var pond Pond + +depinject.Inject( + depinject.Provide( + func() Mallard { return Mallard{} }, + func() Canvasback { return Canvasback{} }, + func(duck Duck) Pond { + return Pond{Duck: duck} + }), + &pond) +``` + +A specific binding preference for `Duck` is required. + +#### `Prefer` API + +In the above situation registering a preference for a given interface binding may look like + +```golang +depinject.Inject( + depinject.Configs( + depinject.Prefer( + "duck.Duck", + "duck.Mallard"), + depinject.Provide( + func() Mallard { return Mallard{} }, + func() Canvasback { return Canvasback{} }), + func(duck Duck) APond { + return Pond{Duck: duck} + }), + &pond) +``` + +Now `depinject` has enough information to provide `Mallard` as an input to `APond`. + ### Full example in real app ```go -//go:embed app.yaml +//ego:embed app.yaml var appConfigYaml []byte var appConfig = appconfig.LoadYAML(appConfigYaml) From ed39682015f39140418cc11533a30a38f57a4329 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 9 Jun 2022 11:29:59 -0500 Subject: [PATCH 31/41] README formatting --- depinject/README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/depinject/README.md b/depinject/README.md index 62aefa3a9c72..87f2d9a16a28 100644 --- a/depinject/README.md +++ b/depinject/README.md @@ -120,15 +120,15 @@ In the above situation registering a preference for a given interface binding ma ```golang depinject.Inject( depinject.Configs( - depinject.Prefer( - "duck.Duck", - "duck.Mallard"), - depinject.Provide( - func() Mallard { return Mallard{} }, - func() Canvasback { return Canvasback{} }), - func(duck Duck) APond { - return Pond{Duck: duck} - }), + depinject.Prefer( + "duck.Duck", + "duck.Mallard"), + depinject.Provide( + func() Mallard { return Mallard{} }, + func() Canvasback { return Canvasback{} }, + func(duck Duck) APond { + return Pond{Duck: duck} + })), &pond) ``` @@ -137,7 +137,7 @@ Now `depinject` has enough information to provide `Mallard` as an input to `APon ### Full example in real app ```go -//ego:embed app.yaml +//go:embed app.yaml var appConfigYaml []byte var appConfig = appconfig.LoadYAML(appConfigYaml) From 9d47bda28322397310a57c9874899c9be6aacdd4 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 9 Jun 2022 11:34:42 -0500 Subject: [PATCH 32/41] README update --- depinject/README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/depinject/README.md b/depinject/README.md index 87f2d9a16a28..a82b1e44d006 100644 --- a/depinject/README.md +++ b/depinject/README.md @@ -5,13 +5,6 @@ `depinject` is a dependency injection framework for the Cosmos SDK. This module together with `core/appconfig` are meant to simplify the definition of a blockchain by replacing most of app.go's boilerplate code with a configuration file (YAML or JSON). -Configuration is provided by a single -[YAML file](https://github.com/cosmos/cosmos-sdk/blob/main/simapp/app.yaml) as specified by -[proto3](https://github.com/cosmos/cosmos-sdk/blob/main/proto/cosmos/auth/module/v1/module.proto) message types. This -configuration is often passed to [provider functions](https://github.com/cosmos/cosmos-sdk/blob/33c4bac3d3acbb820b97173882fa9feddacb2f5a/runtime/module.go#L106-L131) -whose outputs form the concrete dependency tree. Type bindings and tree shape are resolved by reflecting over provider -functions, identifying function parameters as inputs (dependencies) and return values as outputs (tree nodes). - ## Usage `depinject` includes an expressive and composable [Configuration API](https://pkg.go.dev/github.com/cosmos/cosmos-sdk/depinject#Config). From 0c4fa6b43acc079712f14b60e830cab3aacc61d2 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 9 Jun 2022 11:37:17 -0500 Subject: [PATCH 33/41] Fix link --- depinject/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depinject/README.md b/depinject/README.md index a82b1e44d006..018da3b2523f 100644 --- a/depinject/README.md +++ b/depinject/README.md @@ -47,7 +47,7 @@ another provider function or state stored outside the DI container, as is the ca ### Interface type resolution `depinject` supports interface types as inputs to provider functions. In the SDK's case this pattern is used to decouple -`Keeper` dependencies between modules. For example `x/bank` expects an [AccountKeeper] interface as [input to provideModule](https://github.com/cosmos/cosmos-sdk/blob/de343d458aa68c19630177807d6f0e2e6deaf7a9/x/bank/module.go#L224). +`Keeper` dependencies between modules. For example `x/bank` expects an [AccountKeeper](https://pkg.go.dev/github.com/cosmos/cosmos-sdk/x/bank/types#AccountKeeper) interface as [input to provideModule](https://github.com/cosmos/cosmos-sdk/blob/de343d458aa68c19630177807d6f0e2e6deaf7a9/x/bank/module.go#L224). Concretely `SimApp` uses the implementation in `x/auth`, but this design allows for this loose coupling to change. Given the following types From 9e49d793350330adede06d1ddd7d1c158502f541 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Thu, 9 Jun 2022 12:42:43 -0400 Subject: [PATCH 34/41] test(depinject): add preference feature (#12202) * tests(depinject): add preference feature * add features * updates * updates --- depinject/features/prefer.feature | 84 +++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 depinject/features/prefer.feature diff --git a/depinject/features/prefer.feature b/depinject/features/prefer.feature new file mode 100644 index 000000000000..54a8680c2928 --- /dev/null +++ b/depinject/features/prefer.feature @@ -0,0 +1,84 @@ +Feature: interface type resolution + + Background: + Given an interface Duck + And two implementations Mallard and Canvasback + + Rule: interface types resolve to a concrete type implicitly if there is only one matching implementation + Example: only one implementation + Given "Mallard" is provided + When we try to resolve a "Duck" in global scope + Then "Mallard" is resolved in global scope + + Example: two implementations + Given "Mallard" and "Canvasback" are provided + When we try to resolve a "Duck" in global scope + Then there is a "multiple implicit interface bindings" error + + Rule: preferences must point to a real type + Example: + Given "Mallard" is provided + And there is a global preference for a "Marbled" "Duck" + When we try to resolve a "Duck" in global scope + Then there is a "no type for explicit binding" error + + Rule: preferences supersede implicit type resolution + Example: global scope + Given "Canvasback" is provided + And there is a global preference for a "Mallard" "Duck" + When we try to resolve a "Duck" in global scope + Then there is a "can't resolve" error + + Example: module scope + Given "Canvasback" is provided + And there is a preference for a "Mallard" "Duck" in module "A" + When module "A" wants a "Duck" + Then there is a "can't resolve" error + + Rule: preferences in global scope apply to both global and module-scoped resolution (if there is no module-scoped preference) + Example: global resolution + Given "Mallard" and "Canvasback" are provided + And there is a global preference for a "Mallard" "Duck" + When we try to resolve a "Duck" in global scope + Then "Mallard" is resolved in global scope + + Example: module-scoped resolution + Given "Mallard" and "Canvasback" are provided + And there is a global preference for a "Mallard" "Duck" + When module "A" wants a "Duck" + Then module "A" resolves a "Mallard" + + Rule: module-scoped preferences only apply to module-scoped resolution + Example: a module-scoped binding doesn't work for global scope + Given "Mallard" and "Canvasback" are provided + And there is a preference for a "Canvasback" "Duck" in module "A" + When we try to resolve a "Duck" in global scope + Then there is a "multiple implicit interface bindings" error + + Example: a module-scoped binding works for that module + Given "Mallard" and "Canvasback" are provided + And there is a preference for a "Canvasback" "Duck" in module "A" + When module "A" wants a "Duck" + Then module "A" resolves a "Canvasback" + + Example: a module-scoped binding doesn't work for another module + Given "Mallard" and "Canvasback" are provided + And there is a preference for a "Canvasback" "Duck" in module "A" + When module "B" wants a "Duck" + Then there is a "multiple implicit interface bindings" error + + # this case is called a "journey" scenario which tests a bunch of things together + # most tests should be short and to the point like the ones above but one or two long ones + # are good to test more things together &/or do integration tests + Example: two module-scoped preferences and a global preference + Given "Mallard" and "Canvasback" are provided + * there is a global preference for a "Mallard" "Duck" + * there is a preference for a "Canvasback" "Duck" in module "A" + * there is a preference for a "Mallard" "Duck" in module "B" + When module "A" wants a "Duck" + * module "B" wants a "Duck" + * module "C" wants a "Duck" + Then module "A" resolves a "Canvasback" + * module "B" resolves a "Mallard" + * module "C" resolves a "Mallard" + * "Mallard" is resolved in global scope From 9ba8a474aed6bc0a658423420377fe0e7df86ca7 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 9 Jun 2022 15:36:30 -0500 Subject: [PATCH 35/41] Rename preference -> interfaceBinding --- depinject/README.md | 6 ++-- depinject/config.go | 20 ++++++------ depinject/container.go | 61 +++++++++++++++++++++++++++---------- depinject/container_test.go | 12 ++++---- depinject/errors.go | 4 +-- depinject/preference.go | 35 --------------------- 6 files changed, 66 insertions(+), 72 deletions(-) delete mode 100644 depinject/preference.go diff --git a/depinject/README.md b/depinject/README.md index 018da3b2523f..5cb7b6d8b2f8 100644 --- a/depinject/README.md +++ b/depinject/README.md @@ -106,14 +106,14 @@ depinject.Inject( A specific binding preference for `Duck` is required. -#### `Prefer` API +#### `BindInterface` API -In the above situation registering a preference for a given interface binding may look like +In the above situation registering a binding for a given interface binding may look like ```golang depinject.Inject( depinject.Configs( - depinject.Prefer( + depinject.BindInterface( "duck.Duck", "duck.Mallard"), depinject.Provide( diff --git a/depinject/config.go b/depinject/config.go index defc11e170f5..eb1b2a627b1c 100644 --- a/depinject/config.go +++ b/depinject/config.go @@ -48,43 +48,43 @@ func provide(ctr *container, key *moduleKey, providers []interface{}) error { return nil } -// Prefer defines a container configuration for an explicit interface binding of inTypeName to outTypeName +// BindInterface defines a container configuration for an explicit interface binding of inTypeName to outTypeName // in global scope, for example, // -// Prefer( +// BindInterface( // "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", // "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Canvasback") // // configures the container to *always* provide a Canvasback instance when an input of interface type Duck is // requested as an input. -func Prefer(inTypeName string, outTypeName string) Config { +func BindInterface(inTypeName string, outTypeName string) Config { return containerConfig(func(ctr *container) error { - return prefer(ctr, inTypeName, outTypeName, "") + return bindInterface(ctr, inTypeName, outTypeName, "") }) } -// PreferInModule defines a container configuration for an explicit interface binding of inTypeName to outTypeName +// BindInterfaceInModule defines a container configuration for an explicit interface binding of inTypeName to outTypeName // in the scope of the module with name moduleName. For example, given the configuration // -// PreferInModule( +// BindInterfaceInModule( // "moduleFoo", // "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", // "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Canvasback") // // where Duck is an interface and Canvasback implements Duck, the container will attempt to provide a Canvasback // instance where Duck is requested as an input from module "moduleFoo". -func PreferInModule(moduleName string, inTypeName string, outTypeName string) Config { +func BindInterfaceInModule(moduleName string, inTypeName string, outTypeName string) Config { return containerConfig(func(ctr *container) error { - return prefer(ctr, inTypeName, outTypeName, moduleName) + return bindInterface(ctr, inTypeName, outTypeName, moduleName) }) } -func prefer(ctr *container, inTypeName string, outTypeName string, moduleName string) error { +func bindInterface(ctr *container, inTypeName string, outTypeName string, moduleName string) error { var mk *moduleKey if moduleName != "" { mk = &moduleKey{name: moduleName} } - ctr.addPreference(preference{ + ctr.addBinding(interfaceBinding{ interfaceName: inTypeName, implTypeName: outTypeName, moduleKey: mk, diff --git a/depinject/container.go b/depinject/container.go index 16423cc4a4f3..91a95224e2d4 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -11,8 +11,8 @@ import ( type container struct { *debugConfig - resolvers map[string]resolver - preferences map[string]preference + resolvers map[string]resolver + interfaceBindings map[string]interfaceBinding moduleKeys map[string]*moduleKey @@ -26,14 +26,24 @@ type resolveFrame struct { typ reflect.Type } +// interfaceBinding defines a type binding for interfaceName to type implTypeName when being provided as a +// dependency to the module identified by moduleKey. If moduleKey is nil then the type binding is applied globally, +// not module-scoped. +type interfaceBinding struct { + interfaceName string + implTypeName string + moduleKey *moduleKey + resolver resolver +} + func newContainer(cfg *debugConfig) *container { return &container{ - debugConfig: cfg, - resolvers: map[string]resolver{}, - moduleKeys: map[string]*moduleKey{}, - preferences: map[string]preference{}, - callerStack: nil, - callerMap: map[Location]bool{}, + debugConfig: cfg, + resolvers: map[string]resolver{}, + moduleKeys: map[string]*moduleKey{}, + interfaceBindings: map[string]interfaceBinding{}, + callerStack: nil, + callerMap: map[Location]bool{}, } } @@ -79,7 +89,7 @@ func (c *container) call(provider *ProviderDescriptor, moduleKey *moduleKey) ([] func (c *container) getResolver(typ reflect.Type, key *moduleKey) (resolver, error) { c.logf("Resolving %v", typ) - pr, err := c.getPreferredResolver(typ, key) + pr, err := c.getExplicitResolver(typ, key) if err != nil { return nil, err } @@ -156,16 +166,16 @@ func (c *container) getResolver(typ reflect.Type, key *moduleKey) (resolver, err return res, nil } -func (c *container) getPreferredResolver(typ reflect.Type, key *moduleKey) (resolver, error) { - var pref preference +func (c *container) getExplicitResolver(typ reflect.Type, key *moduleKey) (resolver, error) { + var pref interfaceBinding var found bool // module scoped binding takes precedence - pref, found = c.preferences[preferenceKeyFromType(typ, key)] + pref, found = c.interfaceBindings[bindingKeyFromType(typ, key)] // fallback to global scope binding if !found { - pref, found = c.preferences[preferenceKeyFromType(typ, nil)] + pref, found = c.interfaceBindings[bindingKeyFromType(typ, nil)] } if !found { @@ -178,7 +188,7 @@ func (c *container) getPreferredResolver(typ reflect.Type, key *moduleKey) (reso res, ok := c.resolverByTypeName(pref.implTypeName) if ok { - //c.logf("Registering resolver %v for interface type %v by explicit preference", res.getType(), typ) + c.logf("Registering resolver %v for interface type %v by explicit binding", res.getType(), typ) pref.resolver = res return res, nil @@ -476,8 +486,27 @@ func (c container) formatResolveStack() string { return buf.String() } -func (c *container) addPreference(p preference) { - c.preferences[preferenceKeyFromTypeName(p.interfaceName, p.moduleKey)] = p +func fullyQualifiedTypeName(typ reflect.Type) string { + pkgType := typ + if typ.Kind() == reflect.Pointer || typ.Kind() == reflect.Slice || typ.Kind() == reflect.Map || typ.Kind() == reflect.Array { + pkgType = typ.Elem() + } + return fmt.Sprintf("%s/%v", pkgType.PkgPath(), typ) +} + +func bindingKeyFromTypeName(typeName string, key *moduleKey) string { + if key == nil { + return fmt.Sprintf("%s;", typeName) + } + return fmt.Sprintf("%s;%s", typeName, key.name) +} + +func bindingKeyFromType(typ reflect.Type, key *moduleKey) string { + return bindingKeyFromTypeName(fullyQualifiedTypeName(typ), key) +} + +func (c *container) addBinding(p interfaceBinding) { + c.interfaceBindings[bindingKeyFromTypeName(p.interfaceName, p.moduleKey)] = p } func (c *container) addResolver(typ reflect.Type, r resolver) { diff --git a/depinject/container_test.go b/depinject/container_test.go index 60dffc769a40..5d4c2eb25152 100644 --- a/depinject/container_test.go +++ b/depinject/container_test.go @@ -674,7 +674,7 @@ func TestImplicitAndExplicitBindings(t *testing.T) { func(duck Duck) Pond { return Pond{Duck: duck} }) - canvasbackPref := depinject.Prefer( + canvasbackPref := depinject.BindInterface( "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Canvasback") @@ -695,7 +695,7 @@ func TestExplicitModuleBindings(t *testing.T) { require.ErrorContains(t, depinject.Inject( depinject.Configs( - depinject.PreferInModule( + depinject.BindInterfaceInModule( "foo", "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.DoesNotExist"), @@ -712,7 +712,7 @@ func TestExplicitModuleBindings(t *testing.T) { require.NoError(t, depinject.Inject( depinject.Configs( - depinject.PreferInModule( + depinject.BindInterfaceInModule( "foo", "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Mallard"), @@ -728,12 +728,12 @@ func TestExplicitModuleBindings(t *testing.T) { require.IsType(t, pond.Duck, Mallard{}) // module-scoped explicit binding does not interfere with global scope bindings. - // PreferInModule should result in successful resolution of Duck in module-scope, but in global scope there should + // BindInterfaceInModule should result in successful resolution of Duck in module-scope, but in global scope there should // be a MultipleImplementations error. require.ErrorContains(t, depinject.Inject( depinject.Configs( - depinject.PreferInModule( + depinject.BindInterfaceInModule( "foo", "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Mallard"), @@ -754,7 +754,7 @@ func TestExplicitModuleBindings(t *testing.T) { require.NoError(t, depinject.Inject( depinject.Configs( - depinject.Prefer( + depinject.BindInterface( "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Mallard"), depinject.Provide( diff --git a/depinject/errors.go b/depinject/errors.go index 7350d661f0fc..85304e63f263 100644 --- a/depinject/errors.go +++ b/depinject/errors.go @@ -33,7 +33,7 @@ func (err ErrMultipleImplicitInterfaceBindings) Error() string { } // ErrNoTypeForExplicitBindingFound defines an error condition where an explicit binding of PreferredType was marked as -// a preference for Interface, but no provider for the requested PreferredType was found in the container. +// a interfaceBinding for Interface, but no provider for the requested PreferredType was found in the container. type ErrNoTypeForExplicitBindingFound struct { PreferredType string Interface string @@ -41,7 +41,7 @@ type ErrNoTypeForExplicitBindingFound struct { error } -func newErrNoTypeForExplicitBindingFound(p preference) ErrNoTypeForExplicitBindingFound { +func newErrNoTypeForExplicitBindingFound(p interfaceBinding) ErrNoTypeForExplicitBindingFound { var moduleName string if p.moduleKey != nil { moduleName = p.moduleKey.name diff --git a/depinject/preference.go b/depinject/preference.go deleted file mode 100644 index 77a27fc4aa78..000000000000 --- a/depinject/preference.go +++ /dev/null @@ -1,35 +0,0 @@ -package depinject - -import ( - "fmt" - "reflect" -) - -// preference defines a type binding preference to bind interfaceName to type implTypeName when being provided as a -// dependency to the module identified by moduleKey. If moduleKey is nil then the type binding is applied globally, -// not module-scoped. -type preference struct { - interfaceName string - implTypeName string - moduleKey *moduleKey - resolver resolver -} - -func fullyQualifiedTypeName(typ reflect.Type) string { - pkgType := typ - if typ.Kind() == reflect.Pointer || typ.Kind() == reflect.Slice || typ.Kind() == reflect.Map || typ.Kind() == reflect.Array { - pkgType = typ.Elem() - } - return fmt.Sprintf("%s/%v", pkgType.PkgPath(), typ) -} - -func preferenceKeyFromTypeName(typeName string, key *moduleKey) string { - if key == nil { - return fmt.Sprintf("%s;", typeName) - } - return fmt.Sprintf("%s;%s", typeName, key.name) -} - -func preferenceKeyFromType(typ reflect.Type, key *moduleKey) string { - return preferenceKeyFromTypeName(fullyQualifiedTypeName(typ), key) -} From 6dfed89f24c1e8dcb038bf957ccb321f6d86692a Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 9 Jun 2022 15:39:04 -0500 Subject: [PATCH 36/41] Remove more usages of preference language --- depinject/errors.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/depinject/errors.go b/depinject/errors.go index 85304e63f263..6e193cddaa8a 100644 --- a/depinject/errors.go +++ b/depinject/errors.go @@ -32,12 +32,12 @@ func (err ErrMultipleImplicitInterfaceBindings) Error() string { return fmt.Sprintf("Multiple implementations found for interface %v: %s", err.Interface, matchesStr) } -// ErrNoTypeForExplicitBindingFound defines an error condition where an explicit binding of PreferredType was marked as -// a interfaceBinding for Interface, but no provider for the requested PreferredType was found in the container. +// ErrNoTypeForExplicitBindingFound defines an error condition where an explicit binding was specified from Interface +// to Implementation but no provider for the requested Implementation was found in the container. type ErrNoTypeForExplicitBindingFound struct { - PreferredType string - Interface string - ModuleName string + Implementation string + Interface string + ModuleName string error } @@ -48,19 +48,19 @@ func newErrNoTypeForExplicitBindingFound(p interfaceBinding) ErrNoTypeForExplici } return ErrNoTypeForExplicitBindingFound{ - PreferredType: p.implTypeName, - Interface: p.interfaceName, - ModuleName: moduleName, + Implementation: p.implTypeName, + Interface: p.interfaceName, + ModuleName: moduleName, } } func (err ErrNoTypeForExplicitBindingFound) Error() string { if err.ModuleName != "" { return fmt.Sprintf("Given the explicit interface binding %s in module %s, a provider of type %s was not found.", - err.Interface, err.ModuleName, err.PreferredType) + err.Interface, err.ModuleName, err.Implementation) } else { return fmt.Sprintf("Given the explicit interface binding %s, a provider of type %s was not found.", - err.Interface, err.PreferredType) + err.Interface, err.Implementation) } } From cdb2218c0fd821b316e0ebca0f163a105d86d9f6 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Thu, 9 Jun 2022 16:46:54 -0400 Subject: [PATCH 37/41] test(depinject): gocuke implementation for prefer feature (#12206) --- depinject/container_test.go | 133 --------------------------- depinject/errors.go | 4 +- depinject/features/prefer.feature | 52 ++++++----- depinject/go.mod | 7 ++ depinject/go.sum | 22 +++++ depinject/prefer_test.go | 148 ++++++++++++++++++++++++++++++ 6 files changed, 210 insertions(+), 156 deletions(-) create mode 100644 depinject/prefer_test.go diff --git a/depinject/container_test.go b/depinject/container_test.go index 5d4c2eb25152..c1db90d77945 100644 --- a/depinject/container_test.go +++ b/depinject/container_test.go @@ -634,136 +634,3 @@ func TestConditionalDebugging(t *testing.T) { require.Empty(t, logs) require.True(t, success) } - -type Duck interface { - quack() -} - -type AlsoDuck interface { - quack() -} - -type Mallard struct{} -type Canvasback struct{} - -func (duck Mallard) quack() {} -func (duck Canvasback) quack() {} - -type Pond struct { - Duck AlsoDuck -} - -func TestImplicitAndExplicitBindings(t *testing.T) { - var pond Pond - - require.NoError(t, - depinject.Inject( - depinject.Provide( - func() Mallard { return Mallard{} }, - func(duck Duck) Pond { - require.NotNil(t, duck) - return Pond{Duck: duck} - }), - &pond)) - - require.NotNil(t, pond) - - multImplProvider := depinject.Provide( - func() Mallard { return Mallard{} }, - func() Canvasback { return Canvasback{} }, - func(duck Duck) Pond { - return Pond{Duck: duck} - }) - canvasbackPref := depinject.BindInterface( - "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", - "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Canvasback") - - require.ErrorContains(t, depinject.Inject(multImplProvider, &pond), "Multiple implementations") - - require.NoError(t, - depinject.Inject( - depinject.Configs(canvasbackPref, multImplProvider), - &pond)) - - require.IsType(t, pond.Duck, Canvasback{}) - -} - -func TestExplicitModuleBindings(t *testing.T) { - var pond Pond - - require.ErrorContains(t, - depinject.Inject( - depinject.Configs( - depinject.BindInterfaceInModule( - "foo", - "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", - "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.DoesNotExist"), - depinject.Provide( - func() Mallard { return Mallard{} }, - func() Canvasback { return Canvasback{} }), - depinject.ProvideInModule("foo", - func(duck Duck) Pond { - return Pond{Duck: duck} - })), - &pond), "Given the explicit interface binding") - - // naive module-scope binding test - require.NoError(t, - depinject.Inject( - depinject.Configs( - depinject.BindInterfaceInModule( - "foo", - "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", - "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Mallard"), - depinject.Provide( - func(depinject.ModuleKey) Mallard { return Mallard{} }, - func(depinject.ModuleKey) Canvasback { return Canvasback{} }), - depinject.ProvideInModule("foo", - func(duck Duck) Pond { - return Pond{Duck: duck} - })), - &pond)) - - require.IsType(t, pond.Duck, Mallard{}) - - // module-scoped explicit binding does not interfere with global scope bindings. - // BindInterfaceInModule should result in successful resolution of Duck in module-scope, but in global scope there should - // be a MultipleImplementations error. - require.ErrorContains(t, - depinject.Inject( - depinject.Configs( - depinject.BindInterfaceInModule( - "foo", - "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", - "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Mallard"), - depinject.Provide( - func(depinject.ModuleKey) Mallard { return Mallard{} }, - func(depinject.ModuleKey) Canvasback { return Canvasback{} }, - func(duck Duck) Pond { - return Pond{Duck: duck} - }), - depinject.ProvideInModule( - "foo", - func(duck Duck) Pond { - return Pond{Duck: duck} - })), - &pond), "Multiple implementations found for interface") - - // default to global binding if no module scoped binding was found - require.NoError(t, - depinject.Inject( - depinject.Configs( - depinject.BindInterface( - "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Duck", - "github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.Mallard"), - depinject.Provide( - func(depinject.ModuleKey) Mallard { return Mallard{} }, - func(depinject.ModuleKey) Canvasback { return Canvasback{} }), - depinject.ProvideInModule( - "foo", - func(duck Duck) Pond { - return Pond{Duck: duck} - })), - &pond), "Multiple implementations found for interface") -} diff --git a/depinject/errors.go b/depinject/errors.go index 6e193cddaa8a..145ce548a863 100644 --- a/depinject/errors.go +++ b/depinject/errors.go @@ -56,10 +56,10 @@ func newErrNoTypeForExplicitBindingFound(p interfaceBinding) ErrNoTypeForExplici func (err ErrNoTypeForExplicitBindingFound) Error() string { if err.ModuleName != "" { - return fmt.Sprintf("Given the explicit interface binding %s in module %s, a provider of type %s was not found.", + return fmt.Sprintf("No type for explicit binding found. Given the explicit interface binding %s in module %s, a provider of type %s was not found.", err.Interface, err.ModuleName, err.Implementation) } else { - return fmt.Sprintf("Given the explicit interface binding %s, a provider of type %s was not found.", + return fmt.Sprintf("No type for explicit binding found. Given the explicit interface binding %s, a provider of type %s was not found.", err.Interface, err.Implementation) } diff --git a/depinject/features/prefer.feature b/depinject/features/prefer.feature index 54a8680c2928..fea42c46db68 100644 --- a/depinject/features/prefer.feature +++ b/depinject/features/prefer.feature @@ -11,74 +11,84 @@ Feature: interface type resolution Then "Mallard" is resolved in global scope Example: two implementations - Given "Mallard" and "Canvasback" are provided + Given "Mallard" is provided + * "Canvasback" is provided When we try to resolve a "Duck" in global scope - Then there is a "multiple implicit interface bindings" error + Then there is a "Multiple implementations found" error Rule: preferences must point to a real type - Example: + Example: a preferred type is not provided Given "Mallard" is provided And there is a global preference for a "Marbled" "Duck" When we try to resolve a "Duck" in global scope - Then there is a "no type for explicit binding" error + Then there is a "No type for explicit binding" error Rule: preferences supersede implicit type resolution Example: global scope Given "Canvasback" is provided And there is a global preference for a "Mallard" "Duck" When we try to resolve a "Duck" in global scope - Then there is a "can't resolve" error + Then there is a "No type for explicit binding" error Example: module scope Given "Canvasback" is provided And there is a preference for a "Mallard" "Duck" in module "A" When module "A" wants a "Duck" - Then there is a "can't resolve" error + Then there is a "No type for explicit binding" error Rule: preferences in global scope apply to both global and module-scoped resolution (if there is no module-scoped preference) Example: global resolution - Given "Mallard" and "Canvasback" are provided + Given "Mallard" is provided + And "Canvasback" is provided And there is a global preference for a "Mallard" "Duck" When we try to resolve a "Duck" in global scope Then "Mallard" is resolved in global scope Example: module-scoped resolution - Given "Mallard" and "Canvasback" are provided + Given "Mallard" is provided + And "Canvasback" is provided And there is a global preference for a "Mallard" "Duck" When module "A" wants a "Duck" Then module "A" resolves a "Mallard" Rule: module-scoped preferences only apply to module-scoped resolution Example: a module-scoped binding doesn't work for global scope - Given "Mallard" and "Canvasback" are provided - And there is a preference for a "Canvasback" "Duck" in module "A" + Given "Mallard" is provided + * "Canvasback" is provided + * there is a preference for a "Canvasback" "Duck" in module "A" When we try to resolve a "Duck" in global scope - Then there is a "multiple implicit interface bindings" error + Then there is a "Multiple implementations found" error Example: a module-scoped binding works for that module - Given "Mallard" and "Canvasback" are provided - And there is a preference for a "Canvasback" "Duck" in module "A" + Given "Mallard" is provided + * "Canvasback" is provided + * there is a preference for a "Canvasback" "Duck" in module "A" When module "A" wants a "Duck" Then module "A" resolves a "Canvasback" Example: a module-scoped binding doesn't work for another module - Given "Mallard" and "Canvasback" are provided - And there is a preference for a "Canvasback" "Duck" in module "A" + Given "Mallard" is provided + * "Canvasback" is provided + * there is a preference for a "Canvasback" "Duck" in module "A" When module "B" wants a "Duck" - Then there is a "multiple implicit interface bindings" error + Then there is a "Multiple implementations found" error # this case is called a "journey" scenario which tests a bunch of things together # most tests should be short and to the point like the ones above but one or two long ones # are good to test more things together &/or do integration tests Example: two module-scoped preferences and a global preference - Given "Mallard" and "Canvasback" are provided - * there is a global preference for a "Mallard" "Duck" + Given "Mallard" is provided + * "Canvasback" is provided + * "Marbled" is provided + * there is a global preference for a "Marbled" "Duck" * there is a preference for a "Canvasback" "Duck" in module "A" * there is a preference for a "Mallard" "Duck" in module "B" When module "A" wants a "Duck" * module "B" wants a "Duck" * module "C" wants a "Duck" - Then module "A" resolves a "Canvasback" + * we try to resolve a "Duck" in global scope + Then there is no error + * module "A" resolves a "Canvasback" * module "B" resolves a "Mallard" - * module "C" resolves a "Mallard" - * "Mallard" is resolved in global scope + * module "C" resolves a "Marbled" + * "Marbled" is resolved in global scope diff --git a/depinject/go.mod b/depinject/go.mod index 6d012b53b845..7e738836e6c5 100644 --- a/depinject/go.mod +++ b/depinject/go.mod @@ -4,14 +4,21 @@ go 1.18 require ( github.com/pkg/errors v0.9.1 + github.com/regen-network/gocuke v0.6.2 github.com/stretchr/testify v1.7.1 golang.org/x/exp v0.0.0-20220428152302-39d4317da171 gotest.tools/v3 v3.2.0 ) require ( + github.com/alecthomas/participle/v2 v2.0.0-alpha7 // indirect + github.com/cockroachdb/apd/v3 v3.1.0 // indirect + github.com/cucumber/common/gherkin/go/v22 v22.0.0 // indirect + github.com/cucumber/common/messages/go/v17 v17.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gofrs/uuid v4.2.0+incompatible // indirect github.com/google/go-cmp v0.5.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + pgregory.net/rapid v0.4.7 // indirect ) diff --git a/depinject/go.sum b/depinject/go.sum index ca119182a835..57c347b5a126 100644 --- a/depinject/go.sum +++ b/depinject/go.sum @@ -1,14 +1,33 @@ +github.com/alecthomas/participle/v2 v2.0.0-alpha7 h1:cK4vjj0VSgb3lN1nuKA5F7dw+1s1pWBe5bx7nNCnN+c= +github.com/alecthomas/participle/v2 v2.0.0-alpha7/go.mod h1:NumScqsC42o9x+dGj8/YqsIfhrIQjFEOFovxotbBirA= +github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 h1:GDQdwm/gAcJcLAKQQZGOJ4knlw+7rfEQQcmwTbt4p5E= +github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= +github.com/cockroachdb/apd/v3 v3.1.0 h1:MK3Ow7LH0W8zkd5GMKA1PvS9qG3bWFI95WaVNfyZJ/w= +github.com/cockroachdb/apd/v3 v3.1.0/go.mod h1:6qgPBMXjATAdD/VefbRP9NoSLKjbB4LCoA7gN4LpHs4= +github.com/cucumber/common/gherkin/go/v22 v22.0.0 h1:4K8NqptbvdOrjL9DEea6HFjSpbdT9+Q5kgLpmmsHYl0= +github.com/cucumber/common/gherkin/go/v22 v22.0.0/go.mod h1:3mJT10B2GGn3MvVPd3FwR7m2u4tLhSRhWUqJU4KN4Fg= +github.com/cucumber/common/messages/go/v17 v17.1.1 h1:RNqopvIFyLWnKv0LfATh34SWBhXeoFTJnSrgm9cT/Ts= +github.com/cucumber/common/messages/go/v17 v17.1.1/go.mod h1:bpGxb57tDE385Rb2EohgUadLkAbhoC4IyCFi89u/JQI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/regen-network/gocuke v0.6.2 h1:pHviZ0kKAq2U2hN2q3smKNxct6hS0mGByFMHGnWA97M= +github.com/regen-network/gocuke v0.6.2/go.mod h1:zYaqIHZobHyd0xOrHGPQjbhGJsuZ1oElx150u2o1xuk= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -39,8 +58,11 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I= gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= +pgregory.net/rapid v0.4.7 h1:MTNRktPuv5FNqOO151TM9mDTa+XHcX6ypYeISDVD14g= +pgregory.net/rapid v0.4.7/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU= diff --git a/depinject/prefer_test.go b/depinject/prefer_test.go new file mode 100644 index 000000000000..d4b6c844d68b --- /dev/null +++ b/depinject/prefer_test.go @@ -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 TestPrefer(t *testing.T) { + gocuke.NewRunner(t, &preferSuite{}). + Path("features/prefer.feature"). + Step(`we try to resolve a "Duck" in global scope`, (*preferSuite).WeTryToResolveADuckInGlobalScope). + Step(`module "(\w+)" wants a "Duck"`, (*preferSuite).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 preferSuite struct { + gocuke.TestingT // this gets injected by gocuke + + configs []depinject.Config + pond *Pond + err error +} + +func (s preferSuite) AnInterfaceDuck() { + // we don't need to do anything because this is defined at the type level +} + +func (s preferSuite) TwoImplementationsMallardAndCanvasback() { + // we don't need to do anything because this is defined at the type level +} + +func (s *preferSuite) 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 *preferSuite) addConfig(config depinject.Config) { + s.configs = append(s.configs, config) +} + +func (s *preferSuite) WeTryToResolveADuckInGlobalScope() { + s.addConfig(depinject.Provide(func(duck Duck) DuckWrapper { + return DuckWrapper{Module: "", Duck: duck} + })) +} + +func (s *preferSuite) 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 *preferSuite) 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 *preferSuite) ThereIsAError(expectedErrorMsg string) { + s.resolvePond() + assert.ErrorContains(s, s.err, expectedErrorMsg) +} + +func (s *preferSuite) 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 *preferSuite) ThereIsAGlobalPreferenceForA(preferredType string, interfaceType string) { + s.addConfig(depinject.BindInterface(fullTypeName(interfaceType), fullTypeName(preferredType))) +} + +func (s *preferSuite) ThereIsAPreferenceForAInModule(preferredType string, interfaceType string, moduleName string) { + s.addConfig(depinject.BindInterfaceInModule(moduleName, fullTypeName(interfaceType), fullTypeName(preferredType))) +} + +func (s *preferSuite) ModuleWantsADuck(module string) { + s.addConfig(depinject.ProvideInModule(module, func(duck Duck) DuckWrapper { + return DuckWrapper{Module: module, Duck: duck} + })) +} + +func (s *preferSuite) 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) +} From 16caa13f13cf87c443de98b9fc0f145856660bad Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 9 Jun 2022 15:46:06 -0500 Subject: [PATCH 38/41] Fixes after renaming --- depinject/{prefer_test.go => binding_test.go} | 2 +- depinject/features/{prefer.feature => bindings.feature} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename depinject/{prefer_test.go => binding_test.go} (99%) rename depinject/features/{prefer.feature => bindings.feature} (100%) diff --git a/depinject/prefer_test.go b/depinject/binding_test.go similarity index 99% rename from depinject/prefer_test.go rename to depinject/binding_test.go index d4b6c844d68b..de55271a43f7 100644 --- a/depinject/prefer_test.go +++ b/depinject/binding_test.go @@ -15,7 +15,7 @@ import ( func TestPrefer(t *testing.T) { gocuke.NewRunner(t, &preferSuite{}). - Path("features/prefer.feature"). + Path("features/bindings.feature"). Step(`we try to resolve a "Duck" in global scope`, (*preferSuite).WeTryToResolveADuckInGlobalScope). Step(`module "(\w+)" wants a "Duck"`, (*preferSuite).ModuleWantsADuck). Run() diff --git a/depinject/features/prefer.feature b/depinject/features/bindings.feature similarity index 100% rename from depinject/features/prefer.feature rename to depinject/features/bindings.feature From 0324b41bef13bc79b3b6a28bc68142738022ea87 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 9 Jun 2022 16:51:45 -0500 Subject: [PATCH 39/41] Revert README changes --- depinject/README.md | 92 +-------------------------------------------- 1 file changed, 2 insertions(+), 90 deletions(-) diff --git a/depinject/README.md b/depinject/README.md index 5cb7b6d8b2f8..c234bead99de 100644 --- a/depinject/README.md +++ b/depinject/README.md @@ -7,8 +7,7 @@ to simplify the definition of a blockchain by replacing most of app.go's boilerp ## Usage -`depinject` includes an expressive and composable [Configuration API](https://pkg.go.dev/github.com/cosmos/cosmos-sdk/depinject#Config). -A core configuration is `Provide`, for example this code snippet +### `depinject` example ```go package main @@ -16,7 +15,7 @@ package main import ( "fmt" - "github.com/cosmos/cosmos-sdk/depinject" + "cosmossdk.io/depinject" ) type AnotherInt int @@ -40,93 +39,6 @@ func main() { } ``` -demonstrates the registration of free **provider functions** via the `Provide` API. Provider functions form the basis of the -dependency tree, they are introspected then their inputs identified as dependencies and outputs as dependants, either for -another provider function or state stored outside the DI container, as is the case of `&x` and `&y` above. - -### Interface type resolution - -`depinject` supports interface types as inputs to provider functions. In the SDK's case this pattern is used to decouple -`Keeper` dependencies between modules. For example `x/bank` expects an [AccountKeeper](https://pkg.go.dev/github.com/cosmos/cosmos-sdk/x/bank/types#AccountKeeper) interface as [input to provideModule](https://github.com/cosmos/cosmos-sdk/blob/de343d458aa68c19630177807d6f0e2e6deaf7a9/x/bank/module.go#L224). -Concretely `SimApp` uses the implementation in `x/auth`, but this design allows for this loose coupling to change. - -Given the following types - -```golang -package duck - -type Duck interface { - quack() -} - -type AlsoDuck interface { - quack() -} - -type Mallard struct{} -type Canvasback struct{} - -func (duck Mallard) quack() {} -func (duck Canvasback) quack() {} - -type Pond struct { - Duck AlsoDuck -} -``` - -This usage - -```golang -var pond Pond - -depinject.Inject( - depinject.Provide( - func() Mallard { return Mallard{} }, - func(duck Duck) Pond { - return Pond{Duck: duck} - }), - &pond) -``` - -results in an *implicit* binding of `Duck` to `Mallard`. This works because there is only one implementation of `Duck` -in the container. However, adding a second provider of `Duck` will result in an error: - -```golang -var pond Pond - -depinject.Inject( - depinject.Provide( - func() Mallard { return Mallard{} }, - func() Canvasback { return Canvasback{} }, - func(duck Duck) Pond { - return Pond{Duck: duck} - }), - &pond) -``` - -A specific binding preference for `Duck` is required. - -#### `BindInterface` API - -In the above situation registering a binding for a given interface binding may look like - -```golang -depinject.Inject( - depinject.Configs( - depinject.BindInterface( - "duck.Duck", - "duck.Mallard"), - depinject.Provide( - func() Mallard { return Mallard{} }, - func() Canvasback { return Canvasback{} }, - func(duck Duck) APond { - return Pond{Duck: duck} - })), - &pond) -``` - -Now `depinject` has enough information to provide `Mallard` as an input to `APond`. - ### Full example in real app ```go From 0d8f8b8dd008c7a22175843c8943abf2ae385d6b Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 9 Jun 2022 17:02:06 -0500 Subject: [PATCH 40/41] Fix naming in BDD tests --- depinject/binding_test.go | 36 ++++++++++++++--------------- depinject/config.go | 13 ++++------- depinject/features/bindings.feature | 34 +++++++++++++-------------- 3 files changed, 40 insertions(+), 43 deletions(-) diff --git a/depinject/binding_test.go b/depinject/binding_test.go index de55271a43f7..4608dc36d267 100644 --- a/depinject/binding_test.go +++ b/depinject/binding_test.go @@ -13,11 +13,11 @@ import ( "github.com/cosmos/cosmos-sdk/depinject" ) -func TestPrefer(t *testing.T) { - gocuke.NewRunner(t, &preferSuite{}). +func TestBindInterface(t *testing.T) { + gocuke.NewRunner(t, &bindingSuite{}). Path("features/bindings.feature"). - Step(`we try to resolve a "Duck" in global scope`, (*preferSuite).WeTryToResolveADuckInGlobalScope). - Step(`module "(\w+)" wants a "Duck"`, (*preferSuite).ModuleWantsADuck). + Step(`we try to resolve a "Duck" in global scope`, (*bindingSuite).WeTryToResolveADuckInGlobalScope). + Step(`module "(\w+)" wants a "Duck"`, (*bindingSuite).ModuleWantsADuck). Run() } @@ -44,7 +44,7 @@ type Pond struct { Ducks []DuckWrapper } -type preferSuite struct { +type bindingSuite struct { gocuke.TestingT // this gets injected by gocuke configs []depinject.Config @@ -52,15 +52,15 @@ type preferSuite struct { err error } -func (s preferSuite) AnInterfaceDuck() { +func (s bindingSuite) AnInterfaceDuck() { // we don't need to do anything because this is defined at the type level } -func (s preferSuite) TwoImplementationsMallardAndCanvasback() { +func (s bindingSuite) TwoImplementationsMallardAndCanvasback() { // we don't need to do anything because this is defined at the type level } -func (s *preferSuite) IsProvided(a string) { +func (s *bindingSuite) IsProvided(a string) { switch a { case "Mallard": s.addConfig(depinject.Provide(func() Mallard { return Mallard{} })) @@ -73,17 +73,17 @@ func (s *preferSuite) IsProvided(a string) { } } -func (s *preferSuite) addConfig(config depinject.Config) { +func (s *bindingSuite) addConfig(config depinject.Config) { s.configs = append(s.configs, config) } -func (s *preferSuite) WeTryToResolveADuckInGlobalScope() { +func (s *bindingSuite) WeTryToResolveADuckInGlobalScope() { s.addConfig(depinject.Provide(func(duck Duck) DuckWrapper { return DuckWrapper{Module: "", Duck: duck} })) } -func (s *preferSuite) resolvePond() *Pond { +func (s *bindingSuite) resolvePond() *Pond { if s.pond != nil { return s.pond } @@ -95,7 +95,7 @@ func (s *preferSuite) resolvePond() *Pond { return s.pond } -func (s *preferSuite) IsResolvedInGlobalScope(typeName string) { +func (s *bindingSuite) IsResolvedInGlobalScope(typeName string) { pond := s.resolvePond() found := false for _, dw := range pond.Ducks { @@ -107,12 +107,12 @@ func (s *preferSuite) IsResolvedInGlobalScope(typeName string) { assert.True(s, found) } -func (s *preferSuite) ThereIsAError(expectedErrorMsg string) { +func (s *bindingSuite) ThereIsAError(expectedErrorMsg string) { s.resolvePond() assert.ErrorContains(s, s.err, expectedErrorMsg) } -func (s *preferSuite) ThereIsNoError() { +func (s *bindingSuite) ThereIsNoError() { s.resolvePond() assert.NoError(s, s.err) } @@ -121,21 +121,21 @@ func fullTypeName(typeName string) string { return fmt.Sprintf("github.com/cosmos/cosmos-sdk/depinject_test/depinject_test.%s", typeName) } -func (s *preferSuite) ThereIsAGlobalPreferenceForA(preferredType string, interfaceType string) { +func (s *bindingSuite) ThereIsAGlobalBindingForA(preferredType string, interfaceType string) { s.addConfig(depinject.BindInterface(fullTypeName(interfaceType), fullTypeName(preferredType))) } -func (s *preferSuite) ThereIsAPreferenceForAInModule(preferredType string, interfaceType string, moduleName string) { +func (s *bindingSuite) ThereIsABindingForAInModule(preferredType string, interfaceType string, moduleName string) { s.addConfig(depinject.BindInterfaceInModule(moduleName, fullTypeName(interfaceType), fullTypeName(preferredType))) } -func (s *preferSuite) ModuleWantsADuck(module string) { +func (s *bindingSuite) ModuleWantsADuck(module string) { s.addConfig(depinject.ProvideInModule(module, func(duck Duck) DuckWrapper { return DuckWrapper{Module: module, Duck: duck} })) } -func (s *preferSuite) ModuleResolvesA(module string, duckType string) { +func (s *bindingSuite) ModuleResolvesA(module string, duckType string) { pond := s.resolvePond() moduleFound := false for _, dw := range pond.Ducks { diff --git a/depinject/config.go b/depinject/config.go index eb1b2a627b1c..664ad0e1c460 100644 --- a/depinject/config.go +++ b/depinject/config.go @@ -49,14 +49,12 @@ func provide(ctr *container, key *moduleKey, providers []interface{}) error { } // BindInterface defines a container configuration for an explicit interface binding of inTypeName to outTypeName -// in global scope, for example, +// 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") -// -// configures the container to *always* provide a Canvasback instance when an input of interface type Duck is -// requested as an input. func BindInterface(inTypeName string, outTypeName string) Config { return containerConfig(func(ctr *container) error { return bindInterface(ctr, inTypeName, outTypeName, "") @@ -64,15 +62,14 @@ func BindInterface(inTypeName string, outTypeName string) Config { } // BindInterfaceInModule defines a container configuration for an explicit interface binding of inTypeName to outTypeName -// in the scope of the module with name moduleName. For example, given the configuration +// 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") -// -// where Duck is an interface and Canvasback implements Duck, the container will attempt to provide a Canvasback -// instance where Duck is requested as an input from module "moduleFoo". func BindInterfaceInModule(moduleName string, inTypeName string, outTypeName string) Config { return containerConfig(func(ctr *container) error { return bindInterface(ctr, inTypeName, outTypeName, moduleName) diff --git a/depinject/features/bindings.feature b/depinject/features/bindings.feature index fea42c46db68..e3b5410267a1 100644 --- a/depinject/features/bindings.feature +++ b/depinject/features/bindings.feature @@ -16,73 +16,73 @@ Feature: interface type resolution When we try to resolve a "Duck" in global scope Then there is a "Multiple implementations found" error - Rule: preferences must point to a real type - Example: a preferred type is not provided + Rule: bindings must point to a real type + Example: a binding type is not provided Given "Mallard" is provided - And there is a global preference for a "Marbled" "Duck" + And there is a global binding for a "Marbled" "Duck" When we try to resolve a "Duck" in global scope Then there is a "No type for explicit binding" error - Rule: preferences supersede implicit type resolution + Rule: bindings supersede implicit type resolution Example: global scope Given "Canvasback" is provided - And there is a global preference for a "Mallard" "Duck" + And there is a global binding for a "Mallard" "Duck" When we try to resolve a "Duck" in global scope Then there is a "No type for explicit binding" error Example: module scope Given "Canvasback" is provided - And there is a preference for a "Mallard" "Duck" in module "A" + And there is a binding for a "Mallard" "Duck" in module "A" When module "A" wants a "Duck" Then there is a "No type for explicit binding" error - Rule: preferences in global scope apply to both global and module-scoped resolution (if there is no module-scoped preference) + Rule: bindings in global scope apply to both global and module-scoped resolution (if there is no module-scoped binding) Example: global resolution Given "Mallard" is provided And "Canvasback" is provided - And there is a global preference for a "Mallard" "Duck" + And there is a global binding for a "Mallard" "Duck" When we try to resolve a "Duck" in global scope Then "Mallard" is resolved in global scope Example: module-scoped resolution Given "Mallard" is provided And "Canvasback" is provided - And there is a global preference for a "Mallard" "Duck" + And there is a global binding for a "Mallard" "Duck" When module "A" wants a "Duck" Then module "A" resolves a "Mallard" - Rule: module-scoped preferences only apply to module-scoped resolution + Rule: module-scoped binding only apply to module-scoped resolution Example: a module-scoped binding doesn't work for global scope Given "Mallard" is provided * "Canvasback" is provided - * there is a preference for a "Canvasback" "Duck" in module "A" + * there is a binding for a "Canvasback" "Duck" in module "A" When we try to resolve a "Duck" in global scope Then there is a "Multiple implementations found" error Example: a module-scoped binding works for that module Given "Mallard" is provided * "Canvasback" is provided - * there is a preference for a "Canvasback" "Duck" in module "A" + * there is a binding for a "Canvasback" "Duck" in module "A" When module "A" wants a "Duck" Then module "A" resolves a "Canvasback" Example: a module-scoped binding doesn't work for another module Given "Mallard" is provided * "Canvasback" is provided - * there is a preference for a "Canvasback" "Duck" in module "A" + * there is a binding for a "Canvasback" "Duck" in module "A" When module "B" wants a "Duck" Then there is a "Multiple implementations found" error # this case is called a "journey" scenario which tests a bunch of things together # most tests should be short and to the point like the ones above but one or two long ones # are good to test more things together &/or do integration tests - Example: two module-scoped preferences and a global preference + Example: two module-scoped binding and a global binding Given "Mallard" is provided * "Canvasback" is provided * "Marbled" is provided - * there is a global preference for a "Marbled" "Duck" - * there is a preference for a "Canvasback" "Duck" in module "A" - * there is a preference for a "Mallard" "Duck" in module "B" + * there is a global binding for a "Marbled" "Duck" + * there is a binding for a "Canvasback" "Duck" in module "A" + * there is a binding for a "Mallard" "Duck" in module "B" When module "A" wants a "Duck" * module "B" wants a "Duck" * module "C" wants a "Duck" From 7141c61ec3260c66cb963d0086ae2595e7004e2a Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 9 Jun 2022 17:04:17 -0500 Subject: [PATCH 41/41] Fix wording --- depinject/features/bindings.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/depinject/features/bindings.feature b/depinject/features/bindings.feature index e3b5410267a1..fafa61de2b91 100644 --- a/depinject/features/bindings.feature +++ b/depinject/features/bindings.feature @@ -17,7 +17,7 @@ Feature: interface type resolution Then there is a "Multiple implementations found" error Rule: bindings must point to a real type - Example: a binding type is not provided + Example: a bound type is not provided Given "Mallard" is provided And there is a global binding for a "Marbled" "Duck" When we try to resolve a "Duck" in global scope @@ -51,7 +51,7 @@ Feature: interface type resolution When module "A" wants a "Duck" Then module "A" resolves a "Mallard" - Rule: module-scoped binding only apply to module-scoped resolution + Rule: module-scoped bindings only apply to module-scoped resolution Example: a module-scoped binding doesn't work for global scope Given "Mallard" is provided * "Canvasback" is provided