cosmos-sdk/depinject
Jeancarlo Barrios 71035879e4
fix: prevent panic on depinject when input or output struct has an un… (#12786)
…exported fild



## Description

Closes: #1943



---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#pr-targeting))
- [ ] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/main/docs/building-modules)
- [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [ ] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [ ] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed 
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
2022-08-03 03:59:36 +00:00
..
features feat(x/staking): add app wiring support for StakingHooks (#12291) 2022-06-22 15:18:13 +00:00
internal feat(depinject): codegen part 2 types and values (#12616) 2022-08-01 13:57:37 +00:00
testdata refactor: rename depinject imports to cosmossdk.io/depinject (#12479) 2022-07-09 17:46:07 +02:00
binding_test.go refactor: rename depinject imports to cosmossdk.io/depinject (#12479) 2022-07-09 17:46:07 +02:00
config.go refactor: rename depinject imports to cosmossdk.io/depinject (#12479) 2022-07-09 17:46:07 +02:00
container_test.go fix: prevent panic on depinject when input or output struct has an un… (#12786) 2022-08-03 03:59:36 +00:00
container.go fix: prevent panic on depinject when input or output struct has an un… (#12786) 2022-08-03 03:59:36 +00:00
debug.go refactor: rename depinject imports to cosmossdk.io/depinject (#12479) 2022-07-09 17:46:07 +02:00
errors.go fumpt (#12376) 2022-06-28 14:41:42 +01:00
go.mod feat: go workspaces (#12675) 2022-07-26 21:35:31 +02:00
go.sum feat: go workspaces (#12675) 2022-07-26 21:35:31 +02:00
group.go refactor: rename depinject imports to cosmossdk.io/depinject (#12479) 2022-07-09 17:46:07 +02:00
inject.go fix: depinject AutoDebug does not save graphviz file (#12297) 2022-06-22 00:34:05 +02:00
invoke_test.go refactor: rename depinject imports to cosmossdk.io/depinject (#12479) 2022-07-09 17:46:07 +02:00
location.go refactor!: rename container to cosmossdk.io/depinject (#12020) 2022-05-27 19:34:48 +02:00
Makefile refactor!: rename container to cosmossdk.io/depinject (#12020) 2022-05-27 19:34:48 +02:00
module_dep.go refactor: rename depinject imports to cosmossdk.io/depinject (#12479) 2022-07-09 17:46:07 +02:00
module_key.go refactor!: rename container to cosmossdk.io/depinject (#12020) 2022-05-27 19:34:48 +02:00
one_per_module.go refactor: rename depinject imports to cosmossdk.io/depinject (#12479) 2022-07-09 17:46:07 +02:00
provider_desc_test.go refactor: rename depinject imports to cosmossdk.io/depinject (#12479) 2022-07-09 17:46:07 +02:00
provider_desc.go fix(depinject): move non-thread safe write (#12484) 2022-07-07 23:40:32 +02:00
README.md refactor!: use injected encoding params in simapp (#12717) 2022-07-27 15:21:10 +02:00
resolver.go refactor: rename depinject imports to cosmossdk.io/depinject (#12479) 2022-07-09 17:46:07 +02:00
simple.go refactor: rename depinject imports to cosmossdk.io/depinject (#12479) 2022-07-09 17:46:07 +02:00
struct_args.go fix: prevent panic on depinject when input or output struct has an un… (#12786) 2022-08-03 03:59:36 +00:00
supply.go refactor: rename depinject imports to cosmossdk.io/depinject (#12479) 2022-07-09 17:46:07 +02:00

Cosmos SDK Dependency Injection depinject Module

Overview

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).

Usage

depinject includes an expressive and composable Configuration API. A core configuration is Provide, for example this code snippet

package main

import (
	"fmt"

	"cosmossdk.io/depinject"
)

type AnotherInt int

func main() {
	var (
	  x int
	  y AnotherInt
	)

	fmt.Printf("Before (%v, %v)\n", x, y)
	depinject.Inject(
		depinject.Provide(
			func() int { return 1 },
			func() AnotherInt { return AnotherInt(2) },
		),
		&x,
		&y,
	)
	fmt.Printf("After (%v, %v)\n", x, y)
}

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. Concretely SimApp uses the implementation in x/auth, but this design allows for this loose coupling to change.

Given the following types

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

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:

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

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:embed app.yaml
var appConfigYaml []byte

var appConfig = appconfig.LoadYAML(appConfigYaml)

func NewSimApp(
	logger log.Logger,
	db dbm.DB,
	traceStore io.Writer,
	loadLatest bool,
	appOpts servertypes.AppOptions,
	baseAppOptions ...func(*baseapp.BaseApp),
) *SimApp {
	var (
		app        = &SimApp{}
		appBuilder *runtime.AppBuilder
	)

	err := depinject.Inject(AppConfig,
		&appBuilder,
		&app.ParamsKeeper,
		&app.CapabilityKeeper,
		&app.appCodec,
		&app.legacyAmino,
		&app.interfaceRegistry,
		&app.AccountKeeper,
		&app.BankKeeper,
		&app.FeeGrantKeeper,
		&app.StakingKeeper,
	)
	if err != nil {
		panic(err)
	}
...

Debugging

Issues with resolving dependencies in the container can be done with logs and Graphviz renderings of the container tree. By default, whenever there is an error, logs will be printed to stderr and a rendering of the dependency graph in Graphviz DOT format will be saved to debug_container.dot.

Here is an example Graphviz rendering of a successful build of a dependency graph: Graphviz Example

Rectangles represent functions, ovals represent types, rounded rectangles represent modules and the single hexagon represents the function which called Build. Black-colored shapes mark functions and types that were called/resolved without an error. Gray-colored nodes mark functions and types that could have been called/resolved in the container but were left unused.

Here is an example Graphviz rendering of a dependency graph build which failed: Graphviz Error Example

Graphviz DOT files can be converted into SVG's for viewing in a web browser using the dot command-line tool, ex:

> dot -Tsvg debug_container.dot > debug_container.svg

Many other tools including some IDEs support working with DOT files.