package depinject_test import ( "fmt" "reflect" "testing" "github.com/stretchr/testify/require" "cosmossdk.io/depinject" ) type Duck interface { quack() } type ( Mallard struct{} Canvasback struct{} 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 } func IsResolvedInGlobalScope(t *testing.T, pond Pond, typeName string) { t.Helper() found := false for _, dw := range pond.Ducks { if dw.Module == "" { require.Contains(t, reflect.TypeOf(dw.Duck).Name(), typeName) found = true } } require.True(t, found) } func IsResolvedModuleScope(t *testing.T, pond Pond, module, duckType string) { t.Helper() moduleFound := false for _, dw := range pond.Ducks { if dw.Module == module { require.Contains(t, reflect.TypeOf(dw.Duck).Name(), duckType) moduleFound = true } } require.True(t, moduleFound) } func ProvideMallard() Mallard { return Mallard{} } func ProvideCanvasback() Canvasback { return Canvasback{} } func ProvideMarbled() Marbled { return Marbled{} } func ProvideDuckWrapper(duck Duck) DuckWrapper { return DuckWrapper{Module: "", Duck: duck} } func ProvideModuleDuck(duck Duck, key depinject.OwnModuleKey) DuckWrapper { return DuckWrapper{Module: depinject.ModuleKey(key).Name(), Duck: duck} } func ResolvePond(ducks []DuckWrapper) Pond { return Pond{Ducks: ducks} } func fullTypeName(typeName string) string { return fmt.Sprintf("cosmossdk.io/depinject_test/depinject_test.%s", typeName) } func TestProvideNoBinding(t *testing.T) { t.Parallel() configs := depinject.Configs( depinject.Provide( ProvideMallard, ProvideDuckWrapper, ResolvePond, ), ) var pond Pond err := depinject.Inject(configs, &pond) require.NoError(t, err) IsResolvedInGlobalScope(t, pond, "Mallard") } func TestProvideNoBindingImplementationErrorAmbiguous(t *testing.T) { t.Parallel() configs := depinject.Configs( depinject.Provide( ProvideMallard, ProvideCanvasback, ProvideDuckWrapper, ResolvePond, ), ) var pond Pond err := depinject.Inject(configs, &pond) require.ErrorContains(t, err, "Multiple implementations found") } func TestBindInterface(t *testing.T) { t.Parallel() configs := depinject.Configs( depinject.BindInterface(fullTypeName("Duck"), fullTypeName("Mallard")), depinject.Provide( ProvideMallard, ProvideDuckWrapper, ResolvePond, ), ) var pond Pond err := depinject.Inject(configs, &pond) require.NoError(t, err) } func TestBindInterfaceBoundTypeNotProvided(t *testing.T) { t.Parallel() configs := depinject.Configs( depinject.BindInterface(fullTypeName("Duck"), fullTypeName("Marbled")), depinject.Provide( ProvideMallard, ProvideDuckWrapper, ResolvePond, ), ) var pond Pond err := depinject.Inject(configs, &pond) require.ErrorContains(t, err, "No type for explicit binding") } func TestBindInterfaceOverwriteImplicitTypeResolution(t *testing.T) { t.Parallel() configs := depinject.Configs( depinject.BindInterface(fullTypeName("Duck"), fullTypeName("Marbled")), // overwrite Canvasback depinject.Provide( ProvideCanvasback, ProvideDuckWrapper, ResolvePond, ), ) var pond Pond err := depinject.Inject(configs, &pond) require.ErrorContains(t, err, "No type for explicit binding") // same in module scope moduleName := "A" configs = depinject.Configs( depinject.BindInterfaceInModule(moduleName, fullTypeName("Duck"), fullTypeName("Marbled")), // overwrite Canvasback depinject.Provide( ProvideCanvasback, ResolvePond, ), depinject.ProvideInModule(moduleName, ProvideModuleDuck), ) err = depinject.Inject(configs, &pond) require.ErrorContains(t, err, "No type for explicit binding") } func TestBindingInterfaceGlobalScopeApplyToGlobalAndModuleScope(t *testing.T) { t.Parallel() configs := depinject.Configs( depinject.BindInterface(fullTypeName("Duck"), fullTypeName("Mallard")), // order is important depinject.Provide( ProvideMallard, ProvideCanvasback, ProvideDuckWrapper, ResolvePond, ), ) var pond Pond err := depinject.Inject(configs, &pond) require.NoError(t, err) IsResolvedInGlobalScope(t, pond, "Mallard") // same in module scope moduleName := "A" configs = depinject.Configs( depinject.BindInterface(fullTypeName("Duck"), fullTypeName("Mallard")), depinject.Provide( ProvideMallard, ProvideCanvasback, ResolvePond, ), depinject.ProvideInModule(moduleName, ProvideModuleDuck), ) err = depinject.Inject(configs, &pond) require.NoError(t, err) IsResolvedModuleScope(t, pond, moduleName, "Mallard") } func TestBindingInterfaceModuleScopeApplyOnlyModuleScope(t *testing.T) { t.Parallel() moduleName := "A" configs := depinject.Configs( depinject.BindInterfaceInModule(moduleName, fullTypeName("Duck"), fullTypeName("Canvasback")), depinject.Provide( ProvideMallard, ProvideCanvasback, ProvideDuckWrapper, ResolvePond, ), ) var pond Pond err := depinject.Inject(configs, &pond) require.ErrorContains(t, err, "Multiple implementations found") configs = depinject.Configs( depinject.BindInterfaceInModule(moduleName, fullTypeName("Duck"), fullTypeName("Canvasback")), depinject.Provide( ProvideMallard, ProvideCanvasback, ResolvePond, ), depinject.ProvideInModule(moduleName, ProvideModuleDuck), ) err = depinject.Inject(configs, &pond) require.NoError(t, err) IsResolvedModuleScope(t, pond, moduleName, "Canvasback") } func TestBindingInterfaceModuleScopeApplyCorrectModule(t *testing.T) { t.Parallel() moduleName := "A" configs := depinject.Configs( depinject.BindInterfaceInModule(moduleName, fullTypeName("Duck"), fullTypeName("Canvasback")), depinject.Provide( ProvideMallard, ProvideCanvasback, ProvideDuckWrapper, ResolvePond, ), depinject.ProvideInModule("B", ProvideModuleDuck), ) var pond Pond err := depinject.Inject(configs, &pond) require.ErrorContains(t, err, "Multiple implementations found") } func TestBindingInterfaceTwoModuleScopedAndGlobalBinding(t *testing.T) { t.Parallel() moduleA, moduleB, moduleC := "A", "B", "C" configs := depinject.Configs( depinject.BindInterface(fullTypeName("Duck"), fullTypeName("Marbled")), depinject.BindInterfaceInModule(moduleA, fullTypeName("Duck"), fullTypeName("Canvasback")), depinject.BindInterfaceInModule(moduleB, fullTypeName("Duck"), fullTypeName("Mallard")), depinject.Provide( ProvideMallard, ProvideCanvasback, ProvideMarbled, ProvideDuckWrapper, ResolvePond, ), depinject.ProvideInModule(moduleA, ProvideModuleDuck), depinject.ProvideInModule(moduleB, ProvideModuleDuck), depinject.ProvideInModule(moduleC, ProvideModuleDuck), ) var pond Pond err := depinject.Inject(configs, &pond) require.NoError(t, err) IsResolvedModuleScope(t, pond, moduleA, "Canvasback") IsResolvedModuleScope(t, pond, moduleB, "Mallard") IsResolvedModuleScope(t, pond, moduleC, "Marbled") IsResolvedInGlobalScope(t, pond, "Marbled") } func TestIgnoredField(t *testing.T) { t.Parallel() cfg := struct { depinject.In TheDuck Duck privateField bool AnotherDuck Duck }{} err := depinject.Inject(depinject.Provide(ProvideMallard), &cfg) require.NoError(t, err) }