diff --git a/container/config.go b/container/config.go index 7529c4200e29..12437d7c009f 100644 --- a/container/config.go +++ b/container/config.go @@ -13,9 +13,6 @@ import ( ) type config struct { - autoGroupTypes map[reflect.Type]bool - onePerScopeTypes map[reflect.Type]bool - // logging loggers []func(string) indentStr string @@ -35,10 +32,8 @@ func newConfig() (*config, error) { } return &config{ - autoGroupTypes: map[reflect.Type]bool{}, - onePerScopeTypes: map[reflect.Type]bool{}, - graphviz: g, - graph: graph, + graphviz: g, + graph: graph, }, nil } diff --git a/container/container.go b/container/container.go index 8d85213db875..0e031d8f565c 100644 --- a/container/container.go +++ b/container/container.go @@ -27,37 +27,13 @@ type resolveFrame struct { } func newContainer(cfg *config) *container { - ctr := &container{ + return &container{ config: cfg, resolvers: map[reflect.Type]resolver{}, scopes: map[string]Scope{}, callerStack: nil, callerMap: map[Location]bool{}, } - - for typ := range cfg.autoGroupTypes { - sliceType := reflect.SliceOf(typ) - r := &groupResolver{ - typ: typ, - sliceType: sliceType, - } - ctr.resolvers[typ] = r - ctr.resolvers[sliceType] = &sliceGroupResolver{r} - } - - for typ := range cfg.onePerScopeTypes { - mapType := reflect.MapOf(stringType, typ) - r := &onePerScopeResolver{ - typ: typ, - mapType: mapType, - providers: map[Scope]*simpleProvider{}, - idxMap: map[Scope]int{}, - } - ctr.resolvers[typ] = r - ctr.resolvers[mapType] = &mapOfOnePerScopeResolver{r} - } - - return ctr } func (c *container) call(constructor *ProviderDescriptor, scope Scope) ([]reflect.Value, error) { @@ -101,7 +77,7 @@ func (c *container) call(constructor *ProviderDescriptor, scope Scope) ([]reflec return out, nil } -func (c *container) addNode(constructor *ProviderDescriptor, scope Scope, noLog bool) (interface{}, error) { +func (c *container) addNode(constructor *ProviderDescriptor, scope Scope) (interface{}, error) { constructorGraphNode, err := c.locationGraphNode(constructor.Location, scope) if err != nil { return reflect.Value{}, err @@ -122,9 +98,10 @@ func (c *container) addNode(constructor *ProviderDescriptor, scope Scope, noLog } if scope != nil || !hasScopeParam { - if !noLog { - c.logf("Registering provider: %s", constructor.Location.String()) - } + c.logf("Registering %s", constructor.Location.String()) + c.indentLogger() + defer c.dedentLogger() + node := &simpleProvider{ provider: constructor, scope: scope, @@ -137,26 +114,88 @@ func (c *container) addNode(constructor *ProviderDescriptor, scope Scope, noLog for i, out := range constructor.Outputs { typ := out.Type + + // one-per-scope maps can't be used as a return type + if typ.Kind() == reflect.Map && isOnePerScopeType(typ.Elem()) && typ.Key().Kind() == reflect.String { + 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 typ.Kind() == reflect.Slice && c.autoGroupTypes[typ.Elem()] { + if typ.Kind() == reflect.Slice && isAutoGroupType(typ.Elem()) { typ = typ.Elem() } vr, ok := c.resolvers[typ] if ok { + c.logf("Found resolver for %v: %T", typ, vr) err := vr.addNode(node, i, c) if err != nil { return nil, err } } else { - c.resolvers[typ] = &simpleResolver{ - node: node, - typ: typ, - } - - typeGraphNode, err := c.typeGraphNode(typ) - if err != nil { - return reflect.Value{}, err + var typeGraphNode *cgraph.Node + var err error + + if isAutoGroupType(typ) { + c.logf("Registering resolver for auto-group type %v", typ) + sliceType := reflect.SliceOf(typ) + + typeGraphNode, err = c.typeGraphNode(sliceType) + if err != nil { + return reflect.Value{}, err + } + typeGraphNode.SetComment("auto-group") + + r := &groupResolver{ + typ: typ, + sliceType: sliceType, + } + + err = r.addNode(node, i, c) + if err != nil { + return nil, err + } + + c.resolvers[typ] = r + c.resolvers[sliceType] = &sliceGroupResolver{r} + + } else if isOnePerScopeType(typ) { + c.logf("Registering resolver for one-per-scope type %v", typ) + mapType := reflect.MapOf(stringType, typ) + + typeGraphNode, err = c.typeGraphNode(mapType) + if err != nil { + return reflect.Value{}, err + } + typeGraphNode.SetComment("one-per-scope") + + r := &onePerScopeResolver{ + typ: typ, + mapType: mapType, + providers: map[Scope]*simpleProvider{}, + idxMap: map[Scope]int{}, + } + + err = r.addNode(node, i, c) + if err != nil { + return nil, err + } + + c.resolvers[typ] = r + c.resolvers[mapType] = &mapOfOnePerScopeResolver{r} + } else { + c.logf("Registering resolver for simple type %v", typ) + + typeGraphNode, err = c.typeGraphNode(typ) + if err != nil { + return reflect.Value{}, err + } + + c.resolvers[typ] = &simpleResolver{ + node: node, + typ: typ, + } } c.addGraphEdge(constructorGraphNode, typeGraphNode) @@ -165,9 +204,10 @@ func (c *container) addNode(constructor *ProviderDescriptor, scope Scope, noLog return node, nil } else { - if !noLog { - c.logf("Registering scope provider: %s", constructor.Location.String()) - } + c.logf("Registering scope provider: %s", constructor.Location.String()) + c.indentLogger() + defer c.dedentLogger() + node := &scopeDepProvider{ provider: constructor, calledForScope: map[Scope]bool{}, @@ -176,6 +216,9 @@ func (c *container) addNode(constructor *ProviderDescriptor, scope Scope, noLog 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", @@ -280,13 +323,16 @@ func (c *container) run(invoker interface{}) error { return errors.Errorf("invoker function cannot have return values other than error: %s", rctr.Location) } - c.logf("Registering invoker %s", rctr.Location) + c.logf("Registering invoker") + c.indentLogger() - node, err := c.addNode(&rctr, nil, true) + 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") diff --git a/container/container_test.go b/container/container_test.go index e3a0a73400c7..6c5c9ef9d1e5 100644 --- a/container/container_test.go +++ b/container/container_test.go @@ -35,10 +35,14 @@ 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()} } @@ -101,8 +105,6 @@ func TestScenario(t *testing.T) { }, }, b) }, - container.AutoGroupTypes(reflect.TypeOf(Command{})), - container.OnePerScopeTypes(reflect.TypeOf(Handler{})), container.Provide( ProvideKVStoreKey, ProvideModuleKey, @@ -295,45 +297,37 @@ func TestScoped(t *testing.T) { ) } -var intType = reflect.TypeOf(0) +type OnePerScopeInt int -func TestOnePerScope(t *testing.T) { - require.Error(t, - container.Run( - func() {}, - container.OnePerScopeTypes(intType), - container.AutoGroupTypes(intType), - ), - ) +func (OnePerScopeInt) IsOnePerScopeType() {} +func TestOnePerScope(t *testing.T) { require.Error(t, container.Run( - func(int) {}, - container.OnePerScopeTypes(intType), + func(OnePerScopeInt) {}, ), "bad input type", ) require.NoError(t, container.Run( - func(x map[string]int, y string) { - require.Equal(t, map[string]int{ + func(x map[string]OnePerScopeInt, y string) { + require.Equal(t, map[string]OnePerScopeInt{ "a": 3, "b": 4, }, x) require.Equal(t, "7", y) }, - container.OnePerScopeTypes(intType), container.ProvideWithScope("a", - func() int { return 3 }, + func() OnePerScopeInt { return 3 }, ), container.ProvideWithScope("b", - func() int { return 4 }, + func() OnePerScopeInt { return 4 }, ), - container.Provide(func(x map[string]int) string { + container.Provide(func(x map[string]OnePerScopeInt) string { sum := 0 for _, v := range x { - sum += v + sum += int(v) } return fmt.Sprintf("%d", sum) }), @@ -342,11 +336,10 @@ func TestOnePerScope(t *testing.T) { require.Error(t, container.Run( - func(map[string]int) {}, - container.OnePerScopeTypes(intType), + func(map[string]OnePerScopeInt) {}, container.ProvideWithScope("a", - func() int { return 0 }, - func() int { return 0 }, + func() OnePerScopeInt { return 0 }, + func() OnePerScopeInt { return 0 }, ), ), "duplicate", @@ -354,10 +347,9 @@ func TestOnePerScope(t *testing.T) { require.Error(t, container.Run( - func(map[string]int) {}, - container.OnePerScopeTypes(intType), + func(map[string]OnePerScopeInt) {}, container.Provide( - func() int { return 0 }, + func() OnePerScopeInt { return 0 }, ), ), "out of scope", @@ -365,55 +357,35 @@ func TestOnePerScope(t *testing.T) { require.Error(t, container.Run( - func(map[string]int) {}, - container.OnePerScopeTypes(intType), + func(map[string]OnePerScopeInt) {}, container.Provide( - func() map[string]int { return nil }, + func() map[string]OnePerScopeInt { return nil }, ), ), "bad return type", ) } -func TestAutoGroup(t *testing.T) { - require.NoError(t, - container.Run( - func() {}, - container.AutoGroupTypes(intType), - ), - ) +type AutoGroupInt int - require.Error(t, - container.Run( - func() {}, - container.AutoGroupTypes(reflect.SliceOf(intType)), - ), - ) - - require.Error(t, - container.Run( - func() {}, - container.AutoGroupTypes(intType), - container.OnePerScopeTypes(intType), - ), - ) +func (AutoGroupInt) IsAutoGroupType() {} +func TestAutoGroup(t *testing.T) { require.NoError(t, container.Run( - func(xs []int, sum string) { + func(xs []AutoGroupInt, sum string) { require.Len(t, xs, 2) - require.Contains(t, xs, 4) - require.Contains(t, xs, 9) + require.Contains(t, xs, AutoGroupInt(4)) + require.Contains(t, xs, AutoGroupInt(9)) require.Equal(t, "13", sum) }, - container.AutoGroupTypes(intType), container.Provide( - func() int { return 4 }, - func() int { return 9 }, - func(xs []int) string { + func() AutoGroupInt { return 4 }, + func() AutoGroupInt { return 9 }, + func(xs []AutoGroupInt) string { sum := 0 for _, x := range xs { - sum += x + sum += int(x) } return fmt.Sprintf("%d", sum) }, @@ -423,10 +395,9 @@ func TestAutoGroup(t *testing.T) { require.Error(t, container.Run( - func(int) {}, - container.AutoGroupTypes(intType), + func(AutoGroupInt) {}, container.Provide( - func() int { return 0 }, + func() AutoGroupInt { return 0 }, ), ), "bad input type", diff --git a/container/group.go b/container/group.go index d241e5ae7ce5..697f6f2af4b6 100644 --- a/container/group.go +++ b/container/group.go @@ -7,6 +7,21 @@ import ( "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) +} + type groupResolver struct { typ reflect.Type sliceType reflect.Type @@ -26,7 +41,7 @@ func (g *groupResolver) describeLocation() string { func (g *sliceGroupResolver) resolve(c *container, _ Scope, caller Location) (reflect.Value, error) { // Log - c.logf("Providing %v to %s from:", g.sliceType, caller.Name()) + 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()) diff --git a/container/one_per_scope.go b/container/one_per_scope.go index bc2bb34a69dd..8727f5165489 100644 --- a/container/one_per_scope.go +++ b/container/one_per_scope.go @@ -7,6 +7,20 @@ import ( "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) +} + type onePerScopeResolver struct { typ reflect.Type mapType reflect.Type @@ -30,7 +44,7 @@ func (o *onePerScopeResolver) describeLocation() string { func (o *mapOfOnePerScopeResolver) resolve(c *container, _ Scope, caller Location) (reflect.Value, error) { // Log - c.logf("Providing %v to %s from:", o.mapType, caller.Name()) + 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) diff --git a/container/option.go b/container/option.go index 5612442e0d96..9388ab6f64a9 100644 --- a/container/option.go +++ b/container/option.go @@ -43,7 +43,7 @@ func provide(ctr *container, scope Scope, constructors []interface{}) error { if err != nil { return errors.WithStack(err) } - _, err = ctr.addNode(&rc, scope, false) + _, err = ctr.addNode(&rc, scope) if err != nil { return errors.WithStack(err) } @@ -64,54 +64,6 @@ func Supply(values ...interface{}) Option { }) } -// 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 { - return configOption(func(c *config) error { - for _, ty := range types { - if ty.Kind() == reflect.Slice { - return errors.Errorf("slice type %T cannot be used as an auto-group type", ty) - } - - if c.onePerScopeTypes[ty] { - return errors.Errorf("type %v is already registered as a one per scope type, trying to mark as an auto-group type", ty) - } - c.logf("Registering auto-group type %v", ty) - c.autoGroupTypes[ty] = true - node, err := c.typeGraphNode(reflect.SliceOf(ty)) - if err != nil { - return errors.WithStack(err) - } - node.SetComment("auto-group") - } - 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[string]T. -func OnePerScopeTypes(types ...reflect.Type) Option { - return configOption(func(c *config) error { - for _, ty := range types { - if c.autoGroupTypes[ty] { - return errors.Errorf("type %v is already registered as an auto-group type, trying to mark as one per scope type", ty) - } - c.logf("Registering one-per-scope type %v", ty) - c.onePerScopeTypes[ty] = true - node, err := c.typeGraphNode(reflect.MapOf(stringType, ty)) - if err != nil { - return errors.WithStack(err) - } - node.SetComment("one-per-scope") - } - 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 {