feat: implement low-level dependency injection container (#9666)
<!-- The default pull request template is for types feat, fix, or refactor. For other templates, add one of the following parameters to the url: - template=docs.md - template=other.md --> ## Description closes #9775 needs #9658 --- ### 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/master/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/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/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)
This commit is contained in:
parent
d07e41683a
commit
fb78bbfbbb
173
container/config.go
Normal file
173
container/config.go
Normal file
@ -0,0 +1,173 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/goccy/go-graphviz"
|
||||
"github.com/goccy/go-graphviz/cgraph"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
// logging
|
||||
loggers []func(string)
|
||||
indentStr string
|
||||
|
||||
// graphing
|
||||
graphviz *graphviz.Graphviz
|
||||
graph *cgraph.Graph
|
||||
visualizers []func(string)
|
||||
logVisualizer bool
|
||||
}
|
||||
|
||||
func newConfig() (*config, error) {
|
||||
g := graphviz.New()
|
||||
graph, err := g.Graph()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error initializing graph")
|
||||
}
|
||||
|
||||
return &config{
|
||||
graphviz: g,
|
||||
graph: graph,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *config) indentLogger() {
|
||||
c.indentStr = c.indentStr + " "
|
||||
}
|
||||
|
||||
func (c *config) dedentLogger() {
|
||||
if len(c.indentStr) > 0 {
|
||||
c.indentStr = c.indentStr[1:]
|
||||
}
|
||||
}
|
||||
|
||||
func (c config) logf(format string, args ...interface{}) {
|
||||
s := fmt.Sprintf(c.indentStr+format, args...)
|
||||
for _, logger := range c.loggers {
|
||||
logger(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *config) generateGraph() {
|
||||
buf := &bytes.Buffer{}
|
||||
err := c.graphviz.Render(c.graph, graphviz.XDOT, buf)
|
||||
if err != nil {
|
||||
c.logf("Error rendering DOT graph: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
dot := buf.String()
|
||||
if c.logVisualizer {
|
||||
c.logf("DOT Graph: %s", dot)
|
||||
}
|
||||
|
||||
for _, v := range c.visualizers {
|
||||
v(dot)
|
||||
}
|
||||
|
||||
err = c.graph.Close()
|
||||
if err != nil {
|
||||
c.logf("Error closing graph: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = c.graphviz.Close()
|
||||
if err != nil {
|
||||
c.logf("Error closing graphviz: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *config) addFuncVisualizer(f func(string)) {
|
||||
c.visualizers = append(c.visualizers, func(dot string) {
|
||||
f(dot)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *config) enableLogVisualizer() {
|
||||
c.logVisualizer = true
|
||||
}
|
||||
|
||||
func (c *config) addFileVisualizer(filename string, format string) {
|
||||
c.visualizers = append(c.visualizers, func(_ string) {
|
||||
err := c.graphviz.RenderFilename(c.graph, graphviz.Format(format), filename)
|
||||
if err != nil {
|
||||
c.logf("Error saving graphviz file %s with format %s: %+v", filename, format, err)
|
||||
} else {
|
||||
path, err := filepath.Abs(filename)
|
||||
if err == nil {
|
||||
c.logf("Saved graph of container to %s", path)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (c *config) locationGraphNode(location Location, scope Scope) (*cgraph.Node, error) {
|
||||
graph := c.scopeSubGraph(scope)
|
||||
node, found, err := c.findOrCreateGraphNode(graph, location.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if found {
|
||||
return node, nil
|
||||
}
|
||||
|
||||
node.SetShape(cgraph.BoxShape)
|
||||
node.SetColor("lightgrey")
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func (c *config) typeGraphNode(typ reflect.Type) (*cgraph.Node, error) {
|
||||
node, found, err := c.findOrCreateGraphNode(c.graph, typ.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if found {
|
||||
return node, nil
|
||||
}
|
||||
|
||||
node.SetColor("lightgrey")
|
||||
return node, err
|
||||
}
|
||||
|
||||
func (c *config) findOrCreateGraphNode(subGraph *cgraph.Graph, name string) (node *cgraph.Node, found bool, err error) {
|
||||
node, err = c.graph.Node(name)
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrapf(err, "error finding graph node %s", name)
|
||||
}
|
||||
|
||||
if node != nil {
|
||||
return node, true, nil
|
||||
}
|
||||
|
||||
node, err = subGraph.CreateNode(name)
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrapf(err, "error creating graph node %s", name)
|
||||
}
|
||||
|
||||
return node, false, nil
|
||||
}
|
||||
|
||||
func (c *config) scopeSubGraph(scope Scope) *cgraph.Graph {
|
||||
graph := c.graph
|
||||
if scope != nil {
|
||||
gname := fmt.Sprintf("cluster_%s", scope.Name())
|
||||
graph = c.graph.SubGraph(gname, 1)
|
||||
graph.SetLabel(fmt.Sprintf("Scope: %s", scope.Name()))
|
||||
}
|
||||
return graph
|
||||
}
|
||||
|
||||
func (c *config) addGraphEdge(from *cgraph.Node, to *cgraph.Node) {
|
||||
_, err := c.graph.CreateEdge("", from, to)
|
||||
if err != nil {
|
||||
c.logf("error creating graph edge")
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
package container
|
||||
|
||||
import "reflect"
|
||||
|
||||
// ConstructorInfo defines a special constructor type that is defined by
|
||||
// reflection. It should be passed as a value to the Provide function.
|
||||
// Ex:
|
||||
// option.Provide(ConstructorInfo{ ... })
|
||||
type ConstructorInfo struct {
|
||||
// In defines the in parameter types to Fn.
|
||||
In []reflect.Type
|
||||
|
||||
// Out defines the out parameter types to Fn.
|
||||
Out []reflect.Type
|
||||
|
||||
// Fn defines the constructor function.
|
||||
Fn func([]reflect.Value) []reflect.Value
|
||||
|
||||
// Location defines the source code location to be used for this constructor
|
||||
// in error messages.
|
||||
Location Location
|
||||
}
|
||||
406
container/container.go
Normal file
406
container/container.go
Normal file
@ -0,0 +1,406 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/goccy/go-graphviz/cgraph"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type container struct {
|
||||
*config
|
||||
|
||||
resolvers map[reflect.Type]resolver
|
||||
|
||||
scopes map[string]Scope
|
||||
|
||||
resolveStack []resolveFrame
|
||||
callerStack []Location
|
||||
callerMap map[Location]bool
|
||||
}
|
||||
|
||||
type resolveFrame struct {
|
||||
loc Location
|
||||
typ reflect.Type
|
||||
}
|
||||
|
||||
func newContainer(cfg *config) *container {
|
||||
return &container{
|
||||
config: cfg,
|
||||
resolvers: map[reflect.Type]resolver{},
|
||||
scopes: map[string]Scope{},
|
||||
callerStack: nil,
|
||||
callerMap: map[Location]bool{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *container) call(constructor *ProviderDescriptor, scope Scope) ([]reflect.Value, error) {
|
||||
loc := constructor.Location
|
||||
graphNode, err := c.locationGraphNode(loc, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
markGraphNodeAsFailed(graphNode)
|
||||
|
||||
if c.callerMap[loc] {
|
||||
return nil, errors.Errorf("cyclic dependency: %s -> %s", loc.Name(), loc.Name())
|
||||
}
|
||||
|
||||
c.callerMap[loc] = true
|
||||
c.callerStack = append(c.callerStack, loc)
|
||||
|
||||
c.logf("Resolving dependencies for %s", loc)
|
||||
c.indentLogger()
|
||||
inVals := make([]reflect.Value, len(constructor.Inputs))
|
||||
for i, in := range constructor.Inputs {
|
||||
val, err := c.resolve(in, scope, loc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inVals[i] = val
|
||||
}
|
||||
c.dedentLogger()
|
||||
c.logf("Calling %s", loc)
|
||||
|
||||
delete(c.callerMap, loc)
|
||||
c.callerStack = c.callerStack[0 : len(c.callerStack)-1]
|
||||
|
||||
out, err := constructor.Fn(inVals)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error calling constructor %s", loc)
|
||||
}
|
||||
|
||||
markGraphNodeAsUsed(graphNode)
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *container) getResolver(typ reflect.Type) (resolver, error) {
|
||||
if vr, ok := c.resolvers[typ]; ok {
|
||||
return vr, nil
|
||||
}
|
||||
|
||||
elemType := typ
|
||||
if isAutoGroupSliceType(elemType) || isOnePerScopeMapType(elemType) {
|
||||
elemType = elemType.Elem()
|
||||
}
|
||||
|
||||
var typeGraphNode *cgraph.Node
|
||||
var err error
|
||||
|
||||
if isAutoGroupType(elemType) {
|
||||
c.logf("Registering resolver for auto-group type %v", elemType)
|
||||
sliceType := reflect.SliceOf(elemType)
|
||||
|
||||
typeGraphNode, err = c.typeGraphNode(sliceType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typeGraphNode.SetComment("auto-group")
|
||||
|
||||
r := &groupResolver{
|
||||
typ: elemType,
|
||||
sliceType: sliceType,
|
||||
graphNode: typeGraphNode,
|
||||
}
|
||||
|
||||
c.resolvers[elemType] = r
|
||||
c.resolvers[sliceType] = &sliceGroupResolver{r}
|
||||
} else if isOnePerScopeType(elemType) {
|
||||
c.logf("Registering resolver for one-per-scope type %v", elemType)
|
||||
mapType := reflect.MapOf(stringType, elemType)
|
||||
|
||||
typeGraphNode, err = c.typeGraphNode(mapType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typeGraphNode.SetComment("one-per-scope")
|
||||
|
||||
r := &onePerScopeResolver{
|
||||
typ: elemType,
|
||||
mapType: mapType,
|
||||
providers: map[Scope]*simpleProvider{},
|
||||
idxMap: map[Scope]int{},
|
||||
graphNode: typeGraphNode,
|
||||
}
|
||||
|
||||
c.resolvers[elemType] = r
|
||||
c.resolvers[mapType] = &mapOfOnePerScopeResolver{r}
|
||||
}
|
||||
|
||||
return c.resolvers[typ], nil
|
||||
}
|
||||
|
||||
func (c *container) addNode(constructor *ProviderDescriptor, scope Scope) (interface{}, error) {
|
||||
constructorGraphNode, err := c.locationGraphNode(constructor.Location, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hasScopeParam := false
|
||||
for _, in := range constructor.Inputs {
|
||||
typ := in.Type
|
||||
if typ == scopeType {
|
||||
hasScopeParam = true
|
||||
}
|
||||
|
||||
if isAutoGroupType(typ) {
|
||||
return nil, fmt.Errorf("auto-group type %v can't be used as an input parameter", typ)
|
||||
} else if isOnePerScopeType(typ) {
|
||||
return nil, fmt.Errorf("one-per-scope type %v can't be used as an input parameter", typ)
|
||||
}
|
||||
|
||||
vr, err := c.getResolver(typ)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var typeGraphNode *cgraph.Node
|
||||
if vr != nil {
|
||||
typeGraphNode = vr.typeGraphNode()
|
||||
} else {
|
||||
typeGraphNode, err = c.typeGraphNode(typ)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
c.addGraphEdge(typeGraphNode, constructorGraphNode)
|
||||
}
|
||||
|
||||
if scope != nil || !hasScopeParam {
|
||||
c.logf("Registering %s", constructor.Location.String())
|
||||
c.indentLogger()
|
||||
defer c.dedentLogger()
|
||||
|
||||
sp := &simpleProvider{
|
||||
provider: constructor,
|
||||
scope: scope,
|
||||
}
|
||||
|
||||
for i, out := range constructor.Outputs {
|
||||
typ := out.Type
|
||||
|
||||
// one-per-scope maps can't be used as a return type
|
||||
if isOnePerScopeMapType(typ) {
|
||||
return nil, fmt.Errorf("%v cannot be used as a return type because %v is a one-per-scope type",
|
||||
typ, typ.Elem())
|
||||
}
|
||||
|
||||
// auto-group slices of auto-group types
|
||||
if isAutoGroupSliceType(typ) {
|
||||
typ = typ.Elem()
|
||||
}
|
||||
|
||||
vr, err := c.getResolver(typ)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if vr != nil {
|
||||
c.logf("Found resolver for %v: %T", typ, vr)
|
||||
err := vr.addNode(sp, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
c.logf("Registering resolver for simple type %v", typ)
|
||||
|
||||
typeGraphNode, err := c.typeGraphNode(typ)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vr = &simpleResolver{
|
||||
node: sp,
|
||||
typ: typ,
|
||||
graphNode: typeGraphNode,
|
||||
}
|
||||
c.resolvers[typ] = vr
|
||||
}
|
||||
|
||||
c.addGraphEdge(constructorGraphNode, vr.typeGraphNode())
|
||||
}
|
||||
|
||||
return sp, nil
|
||||
} else {
|
||||
c.logf("Registering scope provider: %s", constructor.Location.String())
|
||||
c.indentLogger()
|
||||
defer c.dedentLogger()
|
||||
|
||||
node := &scopeDepProvider{
|
||||
provider: constructor,
|
||||
calledForScope: map[Scope]bool{},
|
||||
valueMap: map[Scope][]reflect.Value{},
|
||||
}
|
||||
|
||||
for i, out := range constructor.Outputs {
|
||||
typ := out.Type
|
||||
|
||||
c.logf("Registering resolver for scoped type %v", typ)
|
||||
|
||||
existing, ok := c.resolvers[typ]
|
||||
if ok {
|
||||
return nil, errors.Errorf("duplicate provision of type %v by scoped provider %s\n\talready provided by %s",
|
||||
typ, constructor.Location, existing.describeLocation())
|
||||
}
|
||||
|
||||
typeGraphNode, err := c.typeGraphNode(typ)
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
|
||||
c.resolvers[typ] = &scopeDepResolver{
|
||||
typ: typ,
|
||||
idxInValues: i,
|
||||
node: node,
|
||||
valueMap: map[Scope]reflect.Value{},
|
||||
graphNode: typeGraphNode,
|
||||
}
|
||||
|
||||
c.addGraphEdge(constructorGraphNode, typeGraphNode)
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *container) supply(value reflect.Value, location Location) error {
|
||||
typ := value.Type()
|
||||
locGrapNode, err := c.locationGraphNode(location, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
markGraphNodeAsUsed(locGrapNode)
|
||||
|
||||
typeGraphNode, err := c.typeGraphNode(typ)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.addGraphEdge(locGrapNode, typeGraphNode)
|
||||
|
||||
if existing, ok := c.resolvers[typ]; ok {
|
||||
return duplicateDefinitionError(typ, location, existing.describeLocation())
|
||||
}
|
||||
|
||||
c.resolvers[typ] = &supplyResolver{
|
||||
typ: typ,
|
||||
value: value,
|
||||
loc: location,
|
||||
graphNode: typeGraphNode,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *container) resolve(in ProviderInput, scope Scope, caller Location) (reflect.Value, error) {
|
||||
c.resolveStack = append(c.resolveStack, resolveFrame{loc: caller, typ: in.Type})
|
||||
|
||||
typeGraphNode, err := c.typeGraphNode(in.Type)
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
|
||||
if in.Type == scopeType {
|
||||
if scope == nil {
|
||||
return reflect.Value{}, errors.Errorf("trying to resolve %T for %s but not inside of any scope", scope, caller)
|
||||
}
|
||||
c.logf("Providing Scope %s", scope.Name())
|
||||
markGraphNodeAsUsed(typeGraphNode)
|
||||
return reflect.ValueOf(scope), nil
|
||||
}
|
||||
|
||||
vr, err := c.getResolver(in.Type)
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
|
||||
if vr == nil {
|
||||
if in.Optional {
|
||||
c.logf("Providing zero value for optional dependency %v", in.Type)
|
||||
return reflect.Zero(in.Type), nil
|
||||
}
|
||||
|
||||
markGraphNodeAsFailed(typeGraphNode)
|
||||
return reflect.Value{}, errors.Errorf("can't resolve type %v for %s:\n%s",
|
||||
in.Type, caller, c.formatResolveStack())
|
||||
}
|
||||
|
||||
res, err := vr.resolve(c, scope, caller)
|
||||
if err != nil {
|
||||
markGraphNodeAsFailed(typeGraphNode)
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
|
||||
markGraphNodeAsUsed(typeGraphNode)
|
||||
|
||||
c.resolveStack = c.resolveStack[:len(c.resolveStack)-1]
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *container) run(invoker interface{}) error {
|
||||
rctr, err := ExtractProviderDescriptor(invoker)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(rctr.Outputs) > 0 {
|
||||
return errors.Errorf("invoker function cannot have return values other than error: %s", rctr.Location)
|
||||
}
|
||||
|
||||
c.logf("Registering invoker")
|
||||
c.indentLogger()
|
||||
|
||||
node, err := c.addNode(&rctr, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.dedentLogger()
|
||||
|
||||
sn, ok := node.(*simpleProvider)
|
||||
if !ok {
|
||||
return errors.Errorf("cannot run scoped provider as an invoker")
|
||||
}
|
||||
|
||||
c.logf("Building container")
|
||||
_, err = sn.resolveValues(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.logf("Done building container")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c container) createOrGetScope(name string) Scope {
|
||||
if s, ok := c.scopes[name]; ok {
|
||||
return s
|
||||
}
|
||||
s := newScope(name)
|
||||
c.scopes[name] = s
|
||||
return s
|
||||
}
|
||||
|
||||
func (c container) formatResolveStack() string {
|
||||
buf := &bytes.Buffer{}
|
||||
_, _ = fmt.Fprintf(buf, "\twhile resolving:\n")
|
||||
n := len(c.resolveStack)
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
rk := c.resolveStack[i]
|
||||
_, _ = fmt.Fprintf(buf, "\t\t%v for %s\n", rk.typ, rk.loc)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func markGraphNodeAsUsed(node *cgraph.Node) {
|
||||
node.SetColor("black")
|
||||
}
|
||||
|
||||
func markGraphNodeAsFailed(node *cgraph.Node) {
|
||||
node.SetColor("red")
|
||||
}
|
||||
@ -1,6 +1,9 @@
|
||||
package container_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@ -32,16 +35,20 @@ type Handler struct {
|
||||
Handle func()
|
||||
}
|
||||
|
||||
func (Handler) IsOnePerScopeType() {}
|
||||
|
||||
type Command struct {
|
||||
Run func()
|
||||
}
|
||||
|
||||
func (Command) IsAutoGroupType() {}
|
||||
|
||||
func ProvideKVStoreKey(scope container.Scope) KVStoreKey {
|
||||
return KVStoreKey{name: scope.Name()}
|
||||
}
|
||||
|
||||
func ProvideModuleKey(scope container.Scope) ModuleKey {
|
||||
return ModuleKey(scope.Name())
|
||||
func ProvideModuleKey(scope container.Scope) (ModuleKey, error) {
|
||||
return ModuleKey(scope.Name()), nil
|
||||
}
|
||||
|
||||
func ProvideMsgClientA(_ container.Scope, key ModuleKey) MsgClientA {
|
||||
@ -57,72 +64,480 @@ func (ModuleA) Provide(key KVStoreKey) (KeeperA, Handler, Command) {
|
||||
type ModuleB struct{}
|
||||
|
||||
type BDependencies struct {
|
||||
container.StructArgs
|
||||
container.In
|
||||
|
||||
Key KVStoreKey
|
||||
A MsgClientA
|
||||
}
|
||||
|
||||
type BProvides struct {
|
||||
container.Out
|
||||
|
||||
KeeperB KeeperB
|
||||
Handler Handler
|
||||
Commands []Command
|
||||
}
|
||||
|
||||
func (ModuleB) Provide(dependencies BDependencies) BProvides {
|
||||
func (ModuleB) Provide(dependencies BDependencies, _ container.Scope) (BProvides, Handler, error) {
|
||||
return BProvides{
|
||||
KeeperB: KeeperB{
|
||||
key: dependencies.Key,
|
||||
msgClientA: dependencies.A,
|
||||
},
|
||||
Handler: Handler{},
|
||||
Commands: []Command{{}, {}},
|
||||
}
|
||||
}, Handler{}, nil
|
||||
}
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
t.Skip("Expecting this test to fail for now")
|
||||
func TestScenario(t *testing.T) {
|
||||
require.NoError(t,
|
||||
container.Run(
|
||||
func(handlers map[container.Scope]Handler, commands []Command, a KeeperA, b KeeperB) {
|
||||
// TODO:
|
||||
// require one Handler for module a and a scopes
|
||||
// require 3 commands
|
||||
// require KeeperA have store key a
|
||||
// require KeeperB have store key b and MsgClientA
|
||||
}),
|
||||
container.AutoGroupTypes(reflect.TypeOf(Command{})),
|
||||
container.OnePerScopeTypes(reflect.TypeOf(Handler{})),
|
||||
func(handlers map[string]Handler, commands []Command, a KeeperA, b KeeperB) {
|
||||
require.Len(t, handlers, 2)
|
||||
require.Equal(t, Handler{}, handlers["a"])
|
||||
require.Equal(t, Handler{}, handlers["b"])
|
||||
require.Len(t, commands, 3)
|
||||
require.Equal(t, KeeperA{
|
||||
key: KVStoreKey{name: "a"},
|
||||
}, a)
|
||||
require.Equal(t, KeeperB{
|
||||
key: KVStoreKey{name: "b"},
|
||||
msgClientA: MsgClientA{
|
||||
key: "b",
|
||||
},
|
||||
}, b)
|
||||
},
|
||||
container.Provide(
|
||||
ProvideKVStoreKey,
|
||||
ProvideModuleKey,
|
||||
ProvideMsgClientA,
|
||||
),
|
||||
container.ProvideWithScope("a", wrapMethod0(ModuleA{})),
|
||||
container.ProvideWithScope("b", wrapMethod0(ModuleB{})),
|
||||
))
|
||||
}
|
||||
|
||||
func wrapMethod0(module interface{}) interface{} {
|
||||
methodFn := reflect.TypeOf(module).Method(0).Func.Interface()
|
||||
ctrInfo, err := container.ExtractProviderDescriptor(methodFn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ctrInfo.Inputs = ctrInfo.Inputs[1:]
|
||||
fn := ctrInfo.Fn
|
||||
ctrInfo.Fn = func(values []reflect.Value) ([]reflect.Value, error) {
|
||||
return fn(append([]reflect.Value{reflect.ValueOf(module)}, values...))
|
||||
}
|
||||
return ctrInfo
|
||||
}
|
||||
|
||||
func TestResolveError(t *testing.T) {
|
||||
require.Error(t, container.Run(
|
||||
func(x string) {},
|
||||
container.Provide(
|
||||
ProvideKVStoreKey,
|
||||
ProvideModuleKey,
|
||||
ProvideMsgClientA,
|
||||
func(x float64) string { return fmt.Sprintf("%f", x) },
|
||||
func(x int) float64 { return float64(x) },
|
||||
func(x float32) int { return int(x) },
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
func TestCyclic(t *testing.T) {
|
||||
require.Error(t, container.Run(
|
||||
func(x string) {},
|
||||
container.Provide(
|
||||
func(x int) float64 { return float64(x) },
|
||||
func(x float64) (int, string) { return int(x), "hi" },
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
func TestErrorOption(t *testing.T) {
|
||||
err := container.Run(func() {}, container.Error(fmt.Errorf("an error")))
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestBadCtr(t *testing.T) {
|
||||
_, err := container.ExtractProviderDescriptor(KeeperA{})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestInvoker(t *testing.T) {
|
||||
require.NoError(t, container.Run(func() {}))
|
||||
require.NoError(t, container.Run(func() error { return nil }))
|
||||
require.Error(t, container.Run(func() error { return fmt.Errorf("error") }))
|
||||
require.Error(t, container.Run(func() int { return 0 }))
|
||||
}
|
||||
|
||||
func TestErrorFunc(t *testing.T) {
|
||||
_, err := container.ExtractProviderDescriptor(
|
||||
func() (error, int) { return nil, 0 },
|
||||
)
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = container.ExtractProviderDescriptor(
|
||||
func() (int, error) { return 0, nil },
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Error(t,
|
||||
container.Run(
|
||||
func(x int) {
|
||||
},
|
||||
container.Provide(func() (int, error) {
|
||||
return 0, fmt.Errorf("the error")
|
||||
}),
|
||||
))
|
||||
|
||||
require.Error(t,
|
||||
container.Run(func() error {
|
||||
return fmt.Errorf("the error")
|
||||
}), "the error")
|
||||
}
|
||||
|
||||
func TestSimple(t *testing.T) {
|
||||
require.NoError(t,
|
||||
container.Run(
|
||||
func(x int) {
|
||||
require.Equal(t, 1, x)
|
||||
},
|
||||
container.Provide(
|
||||
func() int { return 1 },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
require.Error(t,
|
||||
container.Run(func(int) {},
|
||||
container.Provide(
|
||||
func() int { return 0 },
|
||||
func() int { return 1 },
|
||||
),
|
||||
),
|
||||
container.ProvideWithScope(container.NewScope("a"), wrapProvideMethod(ModuleA{})),
|
||||
container.ProvideWithScope(container.NewScope("b"), wrapProvideMethod(ModuleB{})),
|
||||
)
|
||||
}
|
||||
|
||||
func wrapProvideMethod(module interface{}) container.ConstructorInfo {
|
||||
method := reflect.TypeOf(module).Method(0)
|
||||
methodTy := method.Type
|
||||
var in []reflect.Type
|
||||
var out []reflect.Type
|
||||
func TestScoped(t *testing.T) {
|
||||
require.Error(t,
|
||||
container.Run(func(int) {},
|
||||
container.Provide(
|
||||
func(container.Scope) int { return 0 },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
for i := 1; i < methodTy.NumIn(); i++ {
|
||||
in = append(in, methodTy.In(i))
|
||||
}
|
||||
for i := 0; i < methodTy.NumOut(); i++ {
|
||||
out = append(out, methodTy.Out(i))
|
||||
}
|
||||
require.Error(t,
|
||||
container.Run(func(float64) {},
|
||||
container.Provide(
|
||||
func(container.Scope) int { return 0 },
|
||||
func() int { return 1 },
|
||||
),
|
||||
container.ProvideWithScope("a",
|
||||
func(x int) float64 { return float64(x) },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
return container.ConstructorInfo{
|
||||
In: in,
|
||||
Out: out,
|
||||
Fn: func(values []reflect.Value) []reflect.Value {
|
||||
values = append([]reflect.Value{reflect.ValueOf(module)}, values...)
|
||||
return method.Func.Call(values)
|
||||
},
|
||||
Location: container.LocationFromPC(method.Func.Pointer()),
|
||||
}
|
||||
require.Error(t,
|
||||
container.Run(func(float64) {},
|
||||
container.Provide(
|
||||
func() int { return 0 },
|
||||
func(container.Scope) int { return 1 },
|
||||
),
|
||||
container.ProvideWithScope("a",
|
||||
func(x int) float64 { return float64(x) },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
require.Error(t,
|
||||
container.Run(func(float64) {},
|
||||
container.Provide(
|
||||
func(container.Scope) int { return 0 },
|
||||
func(container.Scope) int { return 1 },
|
||||
),
|
||||
container.ProvideWithScope("a",
|
||||
func(x int) float64 { return float64(x) },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
require.NoError(t,
|
||||
container.Run(func(float64) {},
|
||||
container.Provide(
|
||||
func(container.Scope) int { return 0 },
|
||||
),
|
||||
container.ProvideWithScope("a",
|
||||
func(x int) float64 { return float64(x) },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
require.Error(t,
|
||||
container.Run(func(float64) {},
|
||||
container.Provide(
|
||||
func(container.Scope) int { return 0 },
|
||||
),
|
||||
container.ProvideWithScope("",
|
||||
func(x int) float64 { return float64(x) },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
require.NoError(t,
|
||||
container.Run(func(float64, float32) {},
|
||||
container.Provide(
|
||||
func(container.Scope) int { return 0 },
|
||||
),
|
||||
container.ProvideWithScope("a",
|
||||
func(x int) float64 { return float64(x) },
|
||||
func(x int) float32 { return float32(x) },
|
||||
),
|
||||
),
|
||||
"use scope dep twice",
|
||||
)
|
||||
}
|
||||
|
||||
type OnePerScopeInt int
|
||||
|
||||
func (OnePerScopeInt) IsOnePerScopeType() {}
|
||||
|
||||
func TestOnePerScope(t *testing.T) {
|
||||
require.Error(t,
|
||||
container.Run(
|
||||
func(OnePerScopeInt) {},
|
||||
),
|
||||
"bad input type",
|
||||
)
|
||||
|
||||
require.NoError(t,
|
||||
container.Run(
|
||||
func(x map[string]OnePerScopeInt, y string) {
|
||||
require.Equal(t, map[string]OnePerScopeInt{
|
||||
"a": 3,
|
||||
"b": 4,
|
||||
}, x)
|
||||
require.Equal(t, "7", y)
|
||||
},
|
||||
container.ProvideWithScope("a",
|
||||
func() OnePerScopeInt { return 3 },
|
||||
),
|
||||
container.ProvideWithScope("b",
|
||||
func() OnePerScopeInt { return 4 },
|
||||
),
|
||||
container.Provide(func(x map[string]OnePerScopeInt) string {
|
||||
sum := 0
|
||||
for _, v := range x {
|
||||
sum += int(v)
|
||||
}
|
||||
return fmt.Sprintf("%d", sum)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
require.Error(t,
|
||||
container.Run(
|
||||
func(map[string]OnePerScopeInt) {},
|
||||
container.ProvideWithScope("a",
|
||||
func() OnePerScopeInt { return 0 },
|
||||
func() OnePerScopeInt { return 0 },
|
||||
),
|
||||
),
|
||||
"duplicate",
|
||||
)
|
||||
|
||||
require.Error(t,
|
||||
container.Run(
|
||||
func(map[string]OnePerScopeInt) {},
|
||||
container.Provide(
|
||||
func() OnePerScopeInt { return 0 },
|
||||
),
|
||||
),
|
||||
"out of scope",
|
||||
)
|
||||
|
||||
require.Error(t,
|
||||
container.Run(
|
||||
func(map[string]OnePerScopeInt) {},
|
||||
container.Provide(
|
||||
func() map[string]OnePerScopeInt { return nil },
|
||||
),
|
||||
),
|
||||
"bad return type",
|
||||
)
|
||||
|
||||
require.NoError(t,
|
||||
container.Run(
|
||||
func(map[string]OnePerScopeInt) {},
|
||||
),
|
||||
"no providers",
|
||||
)
|
||||
}
|
||||
|
||||
type AutoGroupInt int
|
||||
|
||||
func (AutoGroupInt) IsAutoGroupType() {}
|
||||
|
||||
func TestAutoGroup(t *testing.T) {
|
||||
require.NoError(t,
|
||||
container.Run(
|
||||
func(xs []AutoGroupInt, sum string) {
|
||||
require.Len(t, xs, 2)
|
||||
require.Contains(t, xs, AutoGroupInt(4))
|
||||
require.Contains(t, xs, AutoGroupInt(9))
|
||||
require.Equal(t, "13", sum)
|
||||
},
|
||||
container.Provide(
|
||||
func() AutoGroupInt { return 4 },
|
||||
func() AutoGroupInt { return 9 },
|
||||
func(xs []AutoGroupInt) string {
|
||||
sum := 0
|
||||
for _, x := range xs {
|
||||
sum += int(x)
|
||||
}
|
||||
return fmt.Sprintf("%d", sum)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
require.Error(t,
|
||||
container.Run(
|
||||
func(AutoGroupInt) {},
|
||||
container.Provide(
|
||||
func() AutoGroupInt { return 0 },
|
||||
),
|
||||
),
|
||||
"bad input type",
|
||||
)
|
||||
|
||||
require.NoError(t,
|
||||
container.Run(
|
||||
func([]AutoGroupInt) {},
|
||||
),
|
||||
"no providers",
|
||||
)
|
||||
}
|
||||
|
||||
func TestSupply(t *testing.T) {
|
||||
require.NoError(t,
|
||||
container.Run(func(x int) {
|
||||
require.Equal(t, 3, x)
|
||||
},
|
||||
container.Supply(3),
|
||||
),
|
||||
)
|
||||
|
||||
require.Error(t,
|
||||
container.Run(func(x int) {},
|
||||
container.Supply(3),
|
||||
container.Provide(func() int { return 4 }),
|
||||
),
|
||||
"can't supply then provide",
|
||||
)
|
||||
|
||||
require.Error(t,
|
||||
container.Run(func(x int) {},
|
||||
container.Supply(3),
|
||||
container.Provide(func() int { return 4 }),
|
||||
),
|
||||
"can't provide then supply",
|
||||
)
|
||||
|
||||
require.Error(t,
|
||||
container.Run(func(x int) {},
|
||||
container.Supply(3, 4),
|
||||
),
|
||||
"can't supply twice",
|
||||
)
|
||||
}
|
||||
|
||||
type TestInput struct {
|
||||
container.In
|
||||
|
||||
X int `optional:"true"`
|
||||
Y float64
|
||||
}
|
||||
|
||||
type TestOutput struct {
|
||||
container.Out
|
||||
|
||||
X string
|
||||
}
|
||||
|
||||
func TestStructArgs(t *testing.T) {
|
||||
require.Error(t, container.Run(
|
||||
func(input TestInput) {},
|
||||
))
|
||||
|
||||
require.NoError(t, container.Run(
|
||||
func(input TestInput) {
|
||||
require.Equal(t, 0, input.X)
|
||||
require.Equal(t, 1.3, input.Y)
|
||||
},
|
||||
container.Supply(1.3),
|
||||
))
|
||||
|
||||
require.NoError(t, container.Run(
|
||||
func(input TestInput) {
|
||||
require.Equal(t, 1, input.X)
|
||||
require.Equal(t, 1.3, input.Y)
|
||||
},
|
||||
container.Supply(1.3, 1),
|
||||
))
|
||||
|
||||
require.NoError(t, container.Run(
|
||||
func(x string) {
|
||||
require.Equal(t, "A", x)
|
||||
},
|
||||
container.Provide(func() (TestOutput, error) {
|
||||
return TestOutput{X: "A"}, nil
|
||||
}),
|
||||
))
|
||||
|
||||
require.Error(t, container.Run(
|
||||
func(x string) {},
|
||||
container.Provide(func() (TestOutput, error) {
|
||||
return TestOutput{}, fmt.Errorf("error")
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
func TestLogging(t *testing.T) {
|
||||
var logOut string
|
||||
var dotGraph string
|
||||
|
||||
outfile, err := ioutil.TempFile("", "out")
|
||||
require.NoError(t, err)
|
||||
stdout := os.Stdout
|
||||
os.Stdout = outfile
|
||||
defer func() { os.Stdout = stdout }()
|
||||
defer os.Remove(outfile.Name())
|
||||
|
||||
graphfile, err := ioutil.TempFile("", "graph")
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(graphfile.Name())
|
||||
|
||||
require.NoError(t, container.Run(
|
||||
func() {},
|
||||
container.Logger(func(s string) {
|
||||
logOut += s
|
||||
}),
|
||||
container.Visualizer(func(g string) {
|
||||
dotGraph = g
|
||||
}),
|
||||
container.LogVisualizer(),
|
||||
container.FileVisualizer(graphfile.Name(), "svg"),
|
||||
container.StdoutLogger(),
|
||||
))
|
||||
|
||||
require.Contains(t, logOut, "digraph")
|
||||
require.Contains(t, dotGraph, "digraph")
|
||||
|
||||
outfileContents, err := ioutil.ReadFile(outfile.Name())
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(outfileContents), "digraph")
|
||||
|
||||
graphfileContents, err := ioutil.ReadFile(graphfile.Name())
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(graphfileContents), "<svg")
|
||||
}
|
||||
|
||||
12
container/errors.go
Normal file
12
container/errors.go
Normal file
@ -0,0 +1,12 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
@ -2,10 +2,17 @@ module github.com/cosmos/cosmos-sdk/container
|
||||
|
||||
go 1.17
|
||||
|
||||
require github.com/stretchr/testify v1.7.0
|
||||
require (
|
||||
github.com/goccy/go-graphviz v0.0.9
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.0 // indirect
|
||||
github.com/fogleman/gg v1.3.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
)
|
||||
|
||||
@ -1,10 +1,31 @@
|
||||
github.com/corona10/goimagehash v1.0.2 h1:pUfB0LnsJASMPGEZLj7tGY251vF+qLGqOgEP4rUs6kA=
|
||||
github.com/corona10/goimagehash v1.0.2/go.mod h1:/l9umBhvcHQXVtQO1V6Gp1yD20STawkhRnnX0D1bvVI=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
|
||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/goccy/go-graphviz v0.0.9 h1:s/FMMJ1Joj6La3S5ApO3Jk2cwM4LpXECC2muFx3IPQQ=
|
||||
github.com/goccy/go-graphviz v0.0.9/go.mod h1:wXVsXxmyMQU6TN3zGRttjNn3h+iCAS7xQFC6TlNvLhk=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY=
|
||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg=
|
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
|
||||
95
container/group.go
Normal file
95
container/group.go
Normal file
@ -0,0 +1,95 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/goccy/go-graphviz/cgraph"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// AutoGroupType marks a type which automatically gets grouped together. For an AutoGroupType T,
|
||||
// T and []T can be declared as output parameters for constructors as many times within the container
|
||||
// as desired. All of the provided values for T can be retrieved by declaring an
|
||||
// []T input parameter.
|
||||
type AutoGroupType interface {
|
||||
// IsAutoGroupType is a marker function which just indicates that this is a auto-group type.
|
||||
IsAutoGroupType()
|
||||
}
|
||||
|
||||
var autoGroupTypeType = reflect.TypeOf((*AutoGroupType)(nil)).Elem()
|
||||
|
||||
func isAutoGroupType(t reflect.Type) bool {
|
||||
return t.Implements(autoGroupTypeType)
|
||||
}
|
||||
|
||||
func isAutoGroupSliceType(typ reflect.Type) bool {
|
||||
return typ.Kind() == reflect.Slice && isAutoGroupType(typ.Elem())
|
||||
}
|
||||
|
||||
type groupResolver struct {
|
||||
typ reflect.Type
|
||||
sliceType reflect.Type
|
||||
idxsInValues []int
|
||||
providers []*simpleProvider
|
||||
resolved bool
|
||||
values reflect.Value
|
||||
graphNode *cgraph.Node
|
||||
}
|
||||
|
||||
type sliceGroupResolver struct {
|
||||
*groupResolver
|
||||
}
|
||||
|
||||
func (g *groupResolver) describeLocation() string {
|
||||
return fmt.Sprintf("auto-group type %v", g.typ)
|
||||
}
|
||||
|
||||
func (g *sliceGroupResolver) resolve(c *container, _ Scope, caller Location) (reflect.Value, error) {
|
||||
// Log
|
||||
c.logf("Providing auto-group type slice %v to %s from:", g.sliceType, caller.Name())
|
||||
c.indentLogger()
|
||||
for _, node := range g.providers {
|
||||
c.logf(node.provider.Location.String())
|
||||
}
|
||||
c.dedentLogger()
|
||||
|
||||
// Resolve
|
||||
if !g.resolved {
|
||||
res := reflect.MakeSlice(g.sliceType, 0, 0)
|
||||
for i, node := range g.providers {
|
||||
values, err := node.resolveValues(c)
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
value := values[g.idxsInValues[i]]
|
||||
if value.Kind() == reflect.Slice {
|
||||
n := value.Len()
|
||||
for j := 0; j < n; j++ {
|
||||
res = reflect.Append(res, value.Index(j))
|
||||
}
|
||||
} else {
|
||||
res = reflect.Append(res, value)
|
||||
}
|
||||
}
|
||||
g.values = res
|
||||
g.resolved = true
|
||||
}
|
||||
|
||||
return g.values, nil
|
||||
}
|
||||
|
||||
func (g *groupResolver) resolve(_ *container, _ Scope, _ Location) (reflect.Value, error) {
|
||||
return reflect.Value{}, errors.Errorf("%v is an auto-group type and cannot be used as an input value, instead use %v", g.typ, g.sliceType)
|
||||
}
|
||||
|
||||
func (g *groupResolver) addNode(n *simpleProvider, i int) error {
|
||||
g.providers = append(g.providers, n)
|
||||
g.idxsInValues = append(g.idxsInValues, i)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g groupResolver) typeGraphNode() *cgraph.Node {
|
||||
return g.graphNode
|
||||
}
|
||||
@ -1,19 +1,125 @@
|
||||
// Copyright (c) 2019 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Location describes the source code location of a dependency injection
|
||||
// constructor.
|
||||
type Location interface {
|
||||
isLocation()
|
||||
Name() string
|
||||
fmt.Stringer
|
||||
fmt.Formatter
|
||||
}
|
||||
|
||||
// LocationFromPC builds a Location from a function program counter location,
|
||||
// such as that returned by reflect.Value.Pointer() or runtime.Caller().
|
||||
func LocationFromPC(pc uintptr) Location {
|
||||
panic("TODO")
|
||||
type location struct {
|
||||
name string
|
||||
pkg string
|
||||
file string
|
||||
line int
|
||||
}
|
||||
|
||||
func LocationFromPC(pc uintptr) Location {
|
||||
f := runtime.FuncForPC(pc)
|
||||
pkgName, funcName := splitFuncName(f.Name())
|
||||
fileName, lineNum := f.FileLine(pc)
|
||||
return &location{
|
||||
name: funcName,
|
||||
pkg: pkgName,
|
||||
file: fileName,
|
||||
line: lineNum,
|
||||
}
|
||||
}
|
||||
|
||||
func LocationFromCaller(skip int) Location {
|
||||
pc, _, _, _ := runtime.Caller(skip + 1)
|
||||
return LocationFromPC(pc)
|
||||
}
|
||||
|
||||
func (f *location) isLocation() {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
// String returns a string representation of the function.
|
||||
func (f *location) String() string {
|
||||
return fmt.Sprint(f)
|
||||
}
|
||||
|
||||
// Name is the fully qualified function name.
|
||||
func (f *location) Name() string {
|
||||
return fmt.Sprintf("%v.%v", f.pkg, f.name)
|
||||
}
|
||||
|
||||
// Format implements fmt.Formatter for Func, printing a single-line
|
||||
// representation for %v and a multi-line one for %+v.
|
||||
func (f *location) Format(w fmt.State, c rune) {
|
||||
if w.Flag('+') && c == 'v' {
|
||||
// "path/to/package".MyFunction
|
||||
// path/to/file.go:42
|
||||
_, _ = fmt.Fprintf(w, "%v.%v", f.pkg, f.name)
|
||||
_, _ = fmt.Fprintf(w, "\n\t%v:%v", f.file, f.line)
|
||||
} else {
|
||||
// "path/to/package".MyFunction (path/to/file.go:42)
|
||||
_, _ = fmt.Fprintf(w, "%v.%v (%v:%v)", f.pkg, f.name, f.file, f.line)
|
||||
}
|
||||
}
|
||||
|
||||
const _vendor = "/vendor/"
|
||||
|
||||
func splitFuncName(function string) (pname string, fname string) {
|
||||
if len(function) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// We have something like "path.to/my/pkg.MyFunction". If the function is
|
||||
// a closure, it is something like, "path.to/my/pkg.MyFunction.func1".
|
||||
|
||||
idx := 0
|
||||
|
||||
// Everything up to the first "." after the last "/" is the package name.
|
||||
// Everything after the "." is the full function name.
|
||||
if i := strings.LastIndex(function, "/"); i >= 0 {
|
||||
idx = i
|
||||
}
|
||||
if i := strings.Index(function[idx:], "."); i >= 0 {
|
||||
idx += i
|
||||
}
|
||||
pname, fname = function[:idx], function[idx+1:]
|
||||
|
||||
// The package may be vendored.
|
||||
if i := strings.Index(pname, _vendor); i > 0 {
|
||||
pname = pname[i+len(_vendor):]
|
||||
}
|
||||
|
||||
// Package names are URL-encoded to avoid ambiguity in the case where the
|
||||
// package name contains ".git". Otherwise, "foo/bar.git.MyFunction" would
|
||||
// mean that "git" is the top-level function and "MyFunction" is embedded
|
||||
// inside it.
|
||||
if unescaped, err := url.QueryUnescape(pname); err == nil {
|
||||
pname = unescaped
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
106
container/one_per_scope.go
Normal file
106
container/one_per_scope.go
Normal file
@ -0,0 +1,106 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/goccy/go-graphviz/cgraph"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// OnePerScopeType marks a type which
|
||||
// can have up to one value per scope. All of the values for a one-per-scope type T
|
||||
// and their respective scopes, can be retrieved by declaring an input parameter map[string]T.
|
||||
type OnePerScopeType interface {
|
||||
// IsOnePerScopeType is a marker function just indicates that this is a one-per-scope type.
|
||||
IsOnePerScopeType()
|
||||
}
|
||||
|
||||
var onePerScopeTypeType = reflect.TypeOf((*OnePerScopeType)(nil)).Elem()
|
||||
|
||||
func isOnePerScopeType(t reflect.Type) bool {
|
||||
return t.Implements(onePerScopeTypeType)
|
||||
}
|
||||
|
||||
func isOnePerScopeMapType(typ reflect.Type) bool {
|
||||
return typ.Kind() == reflect.Map && isOnePerScopeType(typ.Elem()) && typ.Key().Kind() == reflect.String
|
||||
}
|
||||
|
||||
type onePerScopeResolver struct {
|
||||
typ reflect.Type
|
||||
mapType reflect.Type
|
||||
providers map[Scope]*simpleProvider
|
||||
idxMap map[Scope]int
|
||||
resolved bool
|
||||
values reflect.Value
|
||||
graphNode *cgraph.Node
|
||||
}
|
||||
|
||||
type mapOfOnePerScopeResolver struct {
|
||||
*onePerScopeResolver
|
||||
}
|
||||
|
||||
func (o *onePerScopeResolver) resolve(_ *container, _ Scope, _ Location) (reflect.Value, error) {
|
||||
return reflect.Value{}, errors.Errorf("%v is a one-per-scope type and thus can't be used as an input parameter, instead use %v", o.typ, o.mapType)
|
||||
}
|
||||
|
||||
func (o *onePerScopeResolver) describeLocation() string {
|
||||
return fmt.Sprintf("one-per-scope type %v", o.typ)
|
||||
}
|
||||
|
||||
func (o *mapOfOnePerScopeResolver) resolve(c *container, _ Scope, caller Location) (reflect.Value, error) {
|
||||
// Log
|
||||
c.logf("Providing one-per-scope type map %v to %s from:", o.mapType, caller.Name())
|
||||
c.indentLogger()
|
||||
for scope, node := range o.providers {
|
||||
c.logf("%s: %s", scope.Name(), node.provider.Location)
|
||||
}
|
||||
c.dedentLogger()
|
||||
|
||||
// Resolve
|
||||
if !o.resolved {
|
||||
res := reflect.MakeMap(o.mapType)
|
||||
for scope, node := range o.providers {
|
||||
values, err := node.resolveValues(c)
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
idx := o.idxMap[scope]
|
||||
if len(values) < idx {
|
||||
return reflect.Value{}, errors.Errorf("expected value of type %T at index %d", o.typ, idx)
|
||||
}
|
||||
value := values[idx]
|
||||
res.SetMapIndex(reflect.ValueOf(scope.Name()), value)
|
||||
}
|
||||
|
||||
o.values = res
|
||||
o.resolved = true
|
||||
}
|
||||
|
||||
return o.values, nil
|
||||
}
|
||||
|
||||
func (o *onePerScopeResolver) addNode(n *simpleProvider, i int) error {
|
||||
if n.scope == nil {
|
||||
return errors.Errorf("cannot define a constructor with one-per-scope dependency %v which isn't provided in a scope", o.typ)
|
||||
}
|
||||
|
||||
if existing, ok := o.providers[n.scope]; ok {
|
||||
return errors.Errorf("duplicate provision for one-per-scope type %v in scope %s: %s\n\talready provided by %s",
|
||||
o.typ, n.scope.Name(), n.provider.Location, existing.provider.Location)
|
||||
}
|
||||
|
||||
o.providers[n.scope] = n
|
||||
o.idxMap[n.scope] = i
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *mapOfOnePerScopeResolver) addNode(s *simpleProvider, _ int) error {
|
||||
return errors.Errorf("%v is a one-per-scope type and thus %v can't be used as an output parameter in %s", o.typ, o.mapType, s.provider.Location)
|
||||
}
|
||||
|
||||
func (o onePerScopeResolver) typeGraphNode() *cgraph.Node {
|
||||
return o.graphNode
|
||||
}
|
||||
@ -1,10 +1,17 @@
|
||||
package container
|
||||
|
||||
import "reflect"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Option is a functional option for a container.
|
||||
type Option interface {
|
||||
isOption()
|
||||
applyConfig(*config) error
|
||||
applyContainer(*container) error
|
||||
}
|
||||
|
||||
// Provide creates a container option which registers the provided dependency
|
||||
@ -12,39 +19,163 @@ type Option interface {
|
||||
// exception of scoped constructors which are called at most once per scope
|
||||
// (see Scope).
|
||||
func Provide(constructors ...interface{}) Option {
|
||||
panic("TODO")
|
||||
return containerOption(func(ctr *container) error {
|
||||
return provide(ctr, nil, constructors)
|
||||
})
|
||||
}
|
||||
|
||||
// ProvideWithScope creates a container option which registers the provided dependency
|
||||
// injection constructors that are to be run in the provided scope. Each constructor
|
||||
// will be called at most once.
|
||||
func ProvideWithScope(scope Scope, constructors ...interface{}) Option {
|
||||
panic("TODO")
|
||||
func ProvideWithScope(scopeName string, constructors ...interface{}) Option {
|
||||
return containerOption(func(ctr *container) error {
|
||||
if scopeName == "" {
|
||||
return errors.Errorf("expected non-empty scope name")
|
||||
}
|
||||
|
||||
return provide(ctr, ctr.createOrGetScope(scopeName), constructors)
|
||||
})
|
||||
}
|
||||
|
||||
// AutoGroupTypes creates an option which registers the provided types as types which
|
||||
// will automatically get grouped together. For a given type T, T and []T can
|
||||
// be declared as output parameters for constructors as many times within the container
|
||||
// as desired. All of the provided values for T can be retrieved by declaring an
|
||||
// []T input parameter.
|
||||
func AutoGroupTypes(types ...reflect.Type) Option {
|
||||
panic("TODO")
|
||||
func provide(ctr *container, scope Scope, constructors []interface{}) error {
|
||||
for _, c := range constructors {
|
||||
rc, err := ExtractProviderDescriptor(c)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
_, err = ctr.addNode(&rc, scope)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnePerScopeTypes creates an option which registers the provided types as types which
|
||||
// can have up to one value per scope. All of the values for a one-per-scope type T
|
||||
// and their respective scopes, can be retrieved by declaring an input parameter map[Scope]T.
|
||||
func OnePerScopeTypes(types ...reflect.Type) Option {
|
||||
panic("TODO")
|
||||
func Supply(values ...interface{}) Option {
|
||||
loc := LocationFromCaller(1)
|
||||
return containerOption(func(ctr *container) error {
|
||||
for _, v := range values {
|
||||
err := ctr.supply(reflect.ValueOf(v), loc)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Logger creates an option which provides a logger function which will
|
||||
// receive all log messages from the container.
|
||||
func Logger(logger func(string)) Option {
|
||||
return configOption(func(c *config) error {
|
||||
logger("Initializing logger")
|
||||
c.loggers = append(c.loggers, logger)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func StdoutLogger() Option {
|
||||
return Logger(func(s string) {
|
||||
_, _ = fmt.Fprintln(os.Stdout, s)
|
||||
})
|
||||
}
|
||||
|
||||
// Visualizer creates an option which provides a visualizer function which
|
||||
// will receive a rendering of the container in the Graphiz DOT format
|
||||
// whenever the container finishes building or fails due to an error. The
|
||||
// graph is color-coded to aid debugging.
|
||||
func Visualizer(visualizer func(dotGraph string)) Option {
|
||||
return configOption(func(c *config) error {
|
||||
c.addFuncVisualizer(visualizer)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func LogVisualizer() Option {
|
||||
return configOption(func(c *config) error {
|
||||
c.enableLogVisualizer()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func FileVisualizer(filename, format string) Option {
|
||||
return configOption(func(c *config) error {
|
||||
c.addFileVisualizer(filename, format)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func Debug() Option {
|
||||
return Options(
|
||||
StdoutLogger(),
|
||||
LogVisualizer(),
|
||||
FileVisualizer("container_dump.svg", "svg"),
|
||||
)
|
||||
}
|
||||
|
||||
// Error creates an option which causes the dependency injection container to
|
||||
// fail immediately.
|
||||
func Error(err error) Option {
|
||||
panic("TODO")
|
||||
return configOption(func(*config) error {
|
||||
return errors.WithStack(err)
|
||||
})
|
||||
}
|
||||
|
||||
// Options creates an option which bundles together other options.
|
||||
func Options(opts ...Option) Option {
|
||||
panic("TODO")
|
||||
return option{
|
||||
configOption: func(cfg *config) error {
|
||||
for _, opt := range opts {
|
||||
err := opt.applyConfig(cfg)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
containerOption: func(ctr *container) error {
|
||||
for _, opt := range opts {
|
||||
err := opt.applyContainer(ctr)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type configOption func(*config) error
|
||||
|
||||
func (c configOption) applyConfig(cfg *config) error {
|
||||
return c(cfg)
|
||||
}
|
||||
|
||||
func (c configOption) applyContainer(*container) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type containerOption func(*container) error
|
||||
|
||||
func (c containerOption) applyConfig(*config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c containerOption) applyContainer(ctr *container) error {
|
||||
return c(ctr)
|
||||
}
|
||||
|
||||
type option struct {
|
||||
configOption
|
||||
containerOption
|
||||
}
|
||||
|
||||
func (o option) applyConfig(c *config) error {
|
||||
return o.configOption(c)
|
||||
}
|
||||
|
||||
func (o option) applyContainer(c *container) error {
|
||||
return o.containerOption(c)
|
||||
}
|
||||
|
||||
var _, _, _ Option = (*configOption)(nil), (*containerOption)(nil), option{}
|
||||
|
||||
104
container/provider_desc.go
Normal file
104
container/provider_desc.go
Normal file
@ -0,0 +1,104 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ProviderDescriptor defines a special constructor type that is defined by
|
||||
// reflection. It should be passed as a value to the Provide function.
|
||||
// Ex:
|
||||
// option.Provide(ProviderDescriptor{ ... })
|
||||
type ProviderDescriptor struct {
|
||||
// Inputs defines the in parameter types to Fn.
|
||||
Inputs []ProviderInput
|
||||
|
||||
// Outputs defines the out parameter types to Fn.
|
||||
Outputs []ProviderOutput
|
||||
|
||||
// Fn defines the constructor function.
|
||||
Fn func([]reflect.Value) ([]reflect.Value, error)
|
||||
|
||||
// Location defines the source code location to be used for this constructor
|
||||
// in error messages.
|
||||
Location Location
|
||||
}
|
||||
|
||||
type ProviderInput struct {
|
||||
Type reflect.Type
|
||||
Optional bool
|
||||
}
|
||||
|
||||
type ProviderOutput struct {
|
||||
Type reflect.Type
|
||||
}
|
||||
|
||||
func ExtractProviderDescriptor(provider interface{}) (ProviderDescriptor, error) {
|
||||
rctr, ok := provider.(ProviderDescriptor)
|
||||
if !ok {
|
||||
var err error
|
||||
rctr, err = doExtractProviderDescriptor(provider)
|
||||
if err != nil {
|
||||
return ProviderDescriptor{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return expandStructArgsConstructor(rctr)
|
||||
}
|
||||
|
||||
func doExtractProviderDescriptor(ctr interface{}) (ProviderDescriptor, error) {
|
||||
val := reflect.ValueOf(ctr)
|
||||
typ := val.Type()
|
||||
if typ.Kind() != reflect.Func {
|
||||
return ProviderDescriptor{}, errors.Errorf("expected a Func type, got %v", typ)
|
||||
}
|
||||
|
||||
loc := LocationFromPC(val.Pointer())
|
||||
|
||||
if typ.IsVariadic() {
|
||||
return ProviderDescriptor{}, errors.Errorf("variadic function can't be used as a constructor: %s", loc)
|
||||
}
|
||||
|
||||
numIn := typ.NumIn()
|
||||
in := make([]ProviderInput, numIn)
|
||||
for i := 0; i < numIn; i++ {
|
||||
in[i] = ProviderInput{
|
||||
Type: typ.In(i),
|
||||
}
|
||||
}
|
||||
|
||||
errIdx := -1
|
||||
numOut := typ.NumOut()
|
||||
var out []ProviderOutput
|
||||
for i := 0; i < numOut; i++ {
|
||||
t := typ.Out(i)
|
||||
if t == errType {
|
||||
if i != numOut-1 {
|
||||
return ProviderDescriptor{}, errors.Errorf("output error parameter is not last parameter in function %s", loc)
|
||||
}
|
||||
errIdx = i
|
||||
} else {
|
||||
out = append(out, ProviderOutput{Type: t})
|
||||
}
|
||||
}
|
||||
|
||||
return ProviderDescriptor{
|
||||
Inputs: in,
|
||||
Outputs: out,
|
||||
Fn: func(values []reflect.Value) ([]reflect.Value, error) {
|
||||
res := val.Call(values)
|
||||
if errIdx >= 0 {
|
||||
err := res[errIdx]
|
||||
if !err.IsZero() {
|
||||
return nil, err.Interface().(error)
|
||||
}
|
||||
return res[0:errIdx], nil
|
||||
}
|
||||
return res, nil
|
||||
},
|
||||
Location: loc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var errType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
106
container/provider_desc_test.go
Normal file
106
container/provider_desc_test.go
Normal file
@ -0,0 +1,106 @@
|
||||
package container_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/container"
|
||||
)
|
||||
|
||||
type StructIn struct {
|
||||
container.In
|
||||
X int
|
||||
Y float64 `optional:"true"`
|
||||
}
|
||||
|
||||
type BadOptional struct {
|
||||
container.In
|
||||
X int `optional:"foo"`
|
||||
}
|
||||
|
||||
type StructOut struct {
|
||||
container.Out
|
||||
X string
|
||||
Y []byte
|
||||
}
|
||||
|
||||
func TestExtractConstructorInfo(t *testing.T) {
|
||||
var (
|
||||
intType = reflect.TypeOf(0)
|
||||
int16Type = reflect.TypeOf(int16(0))
|
||||
int32Type = reflect.TypeOf(int32(0))
|
||||
float32Type = reflect.TypeOf(float32(0.0))
|
||||
float64Type = reflect.TypeOf(0.0)
|
||||
stringType = reflect.TypeOf("")
|
||||
byteTyp = reflect.TypeOf(byte(0))
|
||||
bytesTyp = reflect.TypeOf([]byte{})
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctr interface{}
|
||||
wantIn []container.ProviderInput
|
||||
wantOut []container.ProviderOutput
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"simple args",
|
||||
func(x int, y float64) (string, []byte) { return "", nil },
|
||||
[]container.ProviderInput{{Type: intType}, {Type: float64Type}},
|
||||
[]container.ProviderOutput{{Type: stringType}, {Type: bytesTyp}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"simple args with error",
|
||||
func(x int, y float64) (string, []byte, error) { return "", nil, nil },
|
||||
[]container.ProviderInput{{Type: intType}, {Type: float64Type}},
|
||||
[]container.ProviderOutput{{Type: stringType}, {Type: bytesTyp}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"struct in and out",
|
||||
func(_ float32, _ StructIn, _ byte) (int16, StructOut, int32, error) {
|
||||
return int16(0), StructOut{}, int32(0), nil
|
||||
},
|
||||
[]container.ProviderInput{{Type: float32Type}, {Type: intType}, {Type: float64Type, Optional: true}, {Type: byteTyp}},
|
||||
[]container.ProviderOutput{{Type: int16Type}, {Type: stringType}, {Type: bytesTyp}, {Type: int32Type}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"error bad position",
|
||||
func() (error, int) { return nil, 0 },
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"bad optional",
|
||||
func(_ BadOptional) int { return 0 },
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"variadic",
|
||||
func(...float64) int { return 0 },
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := container.ExtractProviderDescriptor(tt.ctr)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ExtractProviderDescriptor() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got.Inputs, tt.wantIn) {
|
||||
t.Errorf("ExtractProviderDescriptor() got = %v, want %v", got.Inputs, tt.wantIn)
|
||||
}
|
||||
if !reflect.DeepEqual(got.Outputs, tt.wantOut) {
|
||||
t.Errorf("ExtractProviderDescriptor() got = %v, want %v", got.Outputs, tt.wantOut)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
14
container/resolver.go
Normal file
14
container/resolver.go
Normal file
@ -0,0 +1,14 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/goccy/go-graphviz/cgraph"
|
||||
)
|
||||
|
||||
type resolver interface {
|
||||
addNode(*simpleProvider, int) error
|
||||
resolve(*container, Scope, Location) (reflect.Value, error)
|
||||
describeLocation() string
|
||||
typeGraphNode() *cgraph.Node
|
||||
}
|
||||
@ -1,7 +1,5 @@
|
||||
package container
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Run runs the provided invoker function with values provided by the provided
|
||||
// options. It is the single entry point for building and running a dependency
|
||||
// injection container. Invoker should be a function taking one or more
|
||||
@ -10,5 +8,29 @@ import "fmt"
|
||||
// Ex:
|
||||
// Run(func (x int) error { println(x) }, Provide(func() int { return 1 }))
|
||||
func Run(invoker interface{}, opts ...Option) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
opt := Options(opts...)
|
||||
|
||||
cfg, err := newConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer cfg.generateGraph() // always generate graph on exit
|
||||
|
||||
err = opt.applyConfig(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg.logf("Registering providers")
|
||||
cfg.indentLogger()
|
||||
ctr := newContainer(cfg)
|
||||
err = opt.applyContainer(ctr)
|
||||
if err != nil {
|
||||
cfg.logf("Failed registering providers because of: %+v", err)
|
||||
return err
|
||||
}
|
||||
cfg.dedentLogger()
|
||||
|
||||
return ctr.run(invoker)
|
||||
}
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Scope is a special type used to define a provider scope.
|
||||
//
|
||||
// Special scoped constructors can be used with Provide by declaring a
|
||||
// constructor with its first input parameter of type Scope. These constructors
|
||||
// constructor with an input parameter of type Scope. These constructors
|
||||
// should construct an unique value for each dependency based on scope and will
|
||||
// be called at most once per scope.
|
||||
//
|
||||
// Constructors passed to ProvideWithScope can also declare an input parameter
|
||||
// of type Scope to retrieve their scope.
|
||||
// of type Scope to retrieve their scope but these constructors will be called at most once.
|
||||
type Scope interface {
|
||||
isScope()
|
||||
|
||||
@ -18,7 +22,7 @@ type Scope interface {
|
||||
|
||||
// NewScope creates a new scope with the provided name. Only one scope with a
|
||||
// given name can be created per container.
|
||||
func NewScope(name string) Scope {
|
||||
func newScope(name string) Scope {
|
||||
return &scope{name: name}
|
||||
}
|
||||
|
||||
@ -26,8 +30,12 @@ type scope struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (s *scope) isScope() {}
|
||||
|
||||
func (s *scope) Name() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
func (s *scope) isScope() {}
|
||||
|
||||
var scopeType = reflect.TypeOf((*Scope)(nil)).Elem()
|
||||
|
||||
var stringType = reflect.TypeOf("")
|
||||
|
||||
57
container/scope_dep.go
Normal file
57
container/scope_dep.go
Normal file
@ -0,0 +1,57 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/goccy/go-graphviz/cgraph"
|
||||
)
|
||||
|
||||
type scopeDepProvider struct {
|
||||
provider *ProviderDescriptor
|
||||
calledForScope map[Scope]bool
|
||||
valueMap map[Scope][]reflect.Value
|
||||
}
|
||||
|
||||
type scopeDepResolver struct {
|
||||
typ reflect.Type
|
||||
idxInValues int
|
||||
node *scopeDepProvider
|
||||
valueMap map[Scope]reflect.Value
|
||||
graphNode *cgraph.Node
|
||||
}
|
||||
|
||||
func (s scopeDepResolver) describeLocation() string {
|
||||
return s.node.provider.Location.String()
|
||||
}
|
||||
|
||||
func (s scopeDepResolver) resolve(ctr *container, scope Scope, caller Location) (reflect.Value, error) {
|
||||
// Log
|
||||
ctr.logf("Providing %v from %s to %s", s.typ, s.node.provider.Location, caller.Name())
|
||||
|
||||
// Resolve
|
||||
if val, ok := s.valueMap[scope]; ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if !s.node.calledForScope[scope] {
|
||||
values, err := ctr.call(s.node.provider, scope)
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
|
||||
s.node.valueMap[scope] = values
|
||||
s.node.calledForScope[scope] = true
|
||||
}
|
||||
|
||||
value := s.node.valueMap[scope][s.idxInValues]
|
||||
s.valueMap[scope] = value
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (s scopeDepResolver) addNode(p *simpleProvider, _ int) error {
|
||||
return duplicateDefinitionError(s.typ, p.provider.Location, s.node.provider.Location.String())
|
||||
}
|
||||
|
||||
func (s scopeDepResolver) typeGraphNode() *cgraph.Node {
|
||||
return s.graphNode
|
||||
}
|
||||
67
container/simple.go
Normal file
67
container/simple.go
Normal file
@ -0,0 +1,67 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/goccy/go-graphviz/cgraph"
|
||||
)
|
||||
|
||||
type simpleProvider struct {
|
||||
provider *ProviderDescriptor
|
||||
called bool
|
||||
values []reflect.Value
|
||||
scope Scope
|
||||
}
|
||||
|
||||
type simpleResolver struct {
|
||||
node *simpleProvider
|
||||
idxInValues int
|
||||
resolved bool
|
||||
typ reflect.Type
|
||||
value reflect.Value
|
||||
graphNode *cgraph.Node
|
||||
}
|
||||
|
||||
func (s *simpleResolver) describeLocation() string {
|
||||
return s.node.provider.Location.String()
|
||||
}
|
||||
|
||||
func (s *simpleProvider) resolveValues(ctr *container) ([]reflect.Value, error) {
|
||||
if !s.called {
|
||||
values, err := ctr.call(s.provider, s.scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.values = values
|
||||
s.called = true
|
||||
}
|
||||
|
||||
return s.values, nil
|
||||
}
|
||||
|
||||
func (s *simpleResolver) resolve(c *container, _ Scope, caller Location) (reflect.Value, error) {
|
||||
// Log
|
||||
c.logf("Providing %v from %s to %s", s.typ, s.node.provider.Location, caller.Name())
|
||||
|
||||
// Resolve
|
||||
if !s.resolved {
|
||||
values, err := s.node.resolveValues(c)
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
|
||||
value := values[s.idxInValues]
|
||||
s.value = value
|
||||
s.resolved = true
|
||||
}
|
||||
|
||||
return s.value, nil
|
||||
}
|
||||
|
||||
func (s simpleResolver) addNode(p *simpleProvider, _ int) error {
|
||||
return duplicateDefinitionError(s.typ, p.provider.Location, s.node.provider.Location.String())
|
||||
}
|
||||
|
||||
func (s simpleResolver) typeGraphNode() *cgraph.Node {
|
||||
return s.graphNode
|
||||
}
|
||||
@ -1,11 +1,184 @@
|
||||
package container
|
||||
|
||||
// StructArgs is a type which can be embedded in another struct to alert the
|
||||
// container that the fields of the struct are dependency inputs/outputs. That
|
||||
// is, the container will not look to resolve a value with StructArgs embedded
|
||||
// directly, but will instead use the struct's fields to resolve or populate
|
||||
// dependencies. Types with embedded StructArgs can be used in both the input
|
||||
// and output parameter positions.
|
||||
type StructArgs struct{}
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
func (StructArgs) isStructArgs() {}
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// In can be embedded in another struct to inform the container that the
|
||||
// fields of the struct should be treated as dependency inputs.
|
||||
// This allows a struct to be used to specify dependencies rather than
|
||||
// positional parameters.
|
||||
//
|
||||
// Fields of the struct may support the following tags:
|
||||
// optional if set to true, the dependency is optional and will
|
||||
// be set to its default value if not found, rather than causing
|
||||
// an error
|
||||
type In struct{}
|
||||
|
||||
func (In) isIn() {}
|
||||
|
||||
type isIn interface{ isIn() }
|
||||
|
||||
var isInType = reflect.TypeOf((*isIn)(nil)).Elem()
|
||||
|
||||
// Out can be embedded in another struct to inform the container that the
|
||||
// fields of the struct should be treated as dependency outputs.
|
||||
// This allows a struct to be used to specify outputs rather than
|
||||
// positional return values.
|
||||
type Out struct{}
|
||||
|
||||
func (Out) isOut() {}
|
||||
|
||||
type isOut interface{ isOut() }
|
||||
|
||||
var isOutType = reflect.TypeOf((*isOut)(nil)).Elem()
|
||||
|
||||
func expandStructArgsConstructor(constructor ProviderDescriptor) (ProviderDescriptor, error) {
|
||||
var foundStructArgs bool
|
||||
var newIn []ProviderInput
|
||||
|
||||
for _, in := range constructor.Inputs {
|
||||
if in.Type.AssignableTo(isInType) {
|
||||
foundStructArgs = true
|
||||
inTypes, err := structArgsInTypes(in.Type)
|
||||
if err != nil {
|
||||
return ProviderDescriptor{}, err
|
||||
}
|
||||
newIn = append(newIn, inTypes...)
|
||||
} else {
|
||||
newIn = append(newIn, in)
|
||||
}
|
||||
}
|
||||
|
||||
var newOut []ProviderOutput
|
||||
for _, out := range constructor.Outputs {
|
||||
if out.Type.AssignableTo(isOutType) {
|
||||
foundStructArgs = true
|
||||
newOut = append(newOut, structArgsOutTypes(out.Type)...)
|
||||
} else {
|
||||
newOut = append(newOut, out)
|
||||
}
|
||||
}
|
||||
|
||||
if foundStructArgs {
|
||||
return ProviderDescriptor{
|
||||
Inputs: newIn,
|
||||
Outputs: newOut,
|
||||
Fn: expandStructArgsFn(constructor),
|
||||
Location: constructor.Location,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return constructor, nil
|
||||
}
|
||||
|
||||
func expandStructArgsFn(constructor ProviderDescriptor) func(inputs []reflect.Value) ([]reflect.Value, error) {
|
||||
fn := constructor.Fn
|
||||
inParams := constructor.Inputs
|
||||
outParams := constructor.Outputs
|
||||
return func(inputs []reflect.Value) ([]reflect.Value, error) {
|
||||
j := 0
|
||||
inputs1 := make([]reflect.Value, len(inParams))
|
||||
for i, in := range inParams {
|
||||
if in.Type.AssignableTo(isInType) {
|
||||
v, n := buildIn(in.Type, inputs[j:])
|
||||
inputs1[i] = v
|
||||
j += n
|
||||
} else {
|
||||
inputs1[i] = inputs[j]
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
outputs, err := fn(inputs1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var outputs1 []reflect.Value
|
||||
for i, out := range outParams {
|
||||
if out.Type.AssignableTo(isOutType) {
|
||||
outputs1 = append(outputs1, extractFromOut(out.Type, outputs[i])...)
|
||||
} else {
|
||||
outputs1 = append(outputs1, outputs[i])
|
||||
}
|
||||
}
|
||||
|
||||
return outputs1, nil
|
||||
}
|
||||
}
|
||||
|
||||
func structArgsInTypes(typ reflect.Type) ([]ProviderInput, error) {
|
||||
n := typ.NumField()
|
||||
var res []ProviderInput
|
||||
for i := 0; i < n; i++ {
|
||||
f := typ.Field(i)
|
||||
if f.Type.AssignableTo(isInType) {
|
||||
continue
|
||||
}
|
||||
|
||||
var optional bool
|
||||
optTag, found := f.Tag.Lookup("optional")
|
||||
if found {
|
||||
if optTag == "true" {
|
||||
optional = true
|
||||
} else {
|
||||
return nil, errors.Errorf("bad optional tag %q (should be \"true\") in %v", optTag, typ)
|
||||
}
|
||||
}
|
||||
|
||||
res = append(res, ProviderInput{
|
||||
Type: f.Type,
|
||||
Optional: optional,
|
||||
})
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func structArgsOutTypes(typ reflect.Type) []ProviderOutput {
|
||||
n := typ.NumField()
|
||||
var res []ProviderOutput
|
||||
for i := 0; i < n; i++ {
|
||||
f := typ.Field(i)
|
||||
if f.Type.AssignableTo(isOutType) {
|
||||
continue
|
||||
}
|
||||
|
||||
res = append(res, ProviderOutput{
|
||||
Type: f.Type,
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func buildIn(typ reflect.Type, values []reflect.Value) (reflect.Value, int) {
|
||||
numFields := typ.NumField()
|
||||
j := 0
|
||||
res := reflect.New(typ)
|
||||
for i := 0; i < numFields; i++ {
|
||||
f := typ.Field(i)
|
||||
if f.Type.AssignableTo(isInType) {
|
||||
continue
|
||||
}
|
||||
|
||||
res.Elem().Field(i).Set(values[j])
|
||||
j++
|
||||
}
|
||||
return res.Elem(), j
|
||||
}
|
||||
|
||||
func extractFromOut(typ reflect.Type, value reflect.Value) []reflect.Value {
|
||||
numFields := typ.NumField()
|
||||
var res []reflect.Value
|
||||
for i := 0; i < numFields; i++ {
|
||||
f := typ.Field(i)
|
||||
if f.Type.AssignableTo(isOutType) {
|
||||
continue
|
||||
}
|
||||
|
||||
res = append(res, value.Field(i))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
31
container/supply.go
Normal file
31
container/supply.go
Normal file
@ -0,0 +1,31 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/goccy/go-graphviz/cgraph"
|
||||
)
|
||||
|
||||
type supplyResolver struct {
|
||||
typ reflect.Type
|
||||
value reflect.Value
|
||||
loc Location
|
||||
graphNode *cgraph.Node
|
||||
}
|
||||
|
||||
func (s supplyResolver) describeLocation() string {
|
||||
return s.loc.String()
|
||||
}
|
||||
|
||||
func (s supplyResolver) addNode(provider *simpleProvider, _ int) error {
|
||||
return duplicateDefinitionError(s.typ, provider.provider.Location, s.loc.String())
|
||||
}
|
||||
|
||||
func (s supplyResolver) resolve(c *container, _ Scope, caller Location) (reflect.Value, error) {
|
||||
c.logf("Supplying %v from %s to %s", s.typ, s.loc, caller.Name())
|
||||
return s.value, nil
|
||||
}
|
||||
|
||||
func (s supplyResolver) typeGraphNode() *cgraph.Node {
|
||||
return s.graphNode
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user