From faa7a49d65f994312ab132826af7b1062d9080f1 Mon Sep 17 00:00:00 2001 From: yoyofx Date: Tue, 13 Jul 2021 15:12:14 +0800 Subject: [PATCH] di v1.0.0 --- LICENSE | 21 + README.md | 441 ++++++- container.go | 65 + container_test.go | 1375 +------------------- default_serviceprovider.go | 23 +- default_serviceprovider_factory.go | 16 +- di/container.go | 179 +++ di/container_test.go | 719 ++++++++++ di/dot.go | 20 + di/errors.go | 22 + di/internal/ditest/bar.go | 23 + di/internal/ditest/baz.go | 36 + di/internal/ditest/foo.go | 51 + di/internal/ditest/fooer_group.go | 15 + di/internal/ditest/full.go | 83 ++ di/internal/ditest/incorrect.go | 16 + di/internal/ditest/interfaces.go | 16 + di/internal/ditest/qux.go | 15 + di/internal/graphkv/directed_graph.go | 149 +++ di/internal/graphkv/directed_graph_test.go | 146 +++ di/internal/graphkv/edge.go | 125 ++ di/internal/graphkv/errors.go | 22 + di/internal/graphkv/graph.go | 60 + di/internal/graphkv/graph_test.go | 111 ++ di/internal/graphkv/graphkv.go | 75 ++ di/internal/graphkv/graphkv_test.go | 3 + di/internal/graphkv/key.go | 75 ++ di/internal/graphkv/output.go | 62 + di/internal/graphkv/output_test.go | 1 + di/internal/graphkv/sort.go | 170 +++ di/internal/graphkv/sort_test.go | 86 ++ di/internal/reflection/func.go | 35 + di/internal/reflection/iface.go | 25 + di/internal/reflection/reflection.go | 20 + di/invoker.go | 78 ++ di/key.go | 62 + di/options.go | 64 + di/panic.go | 7 + di/parameter.go | 75 ++ di/parameter_bag.go | 98 ++ di/parameter_bag_test.go | 142 ++ di/parameter_list.go | 20 + di/provider.go | 27 + di/provider_ctor.go | 127 ++ di/provider_embed.go | 96 ++ di/provider_group.go | 50 + di/provider_iface.go | 47 + di/provider_stub.go | 29 + di/singleton.go | 27 + di_test.go | 10 - go.mod | 4 +- graph.png | Bin 0 -> 134199 bytes options.go | 180 +++ options_test.go | 50 + serviceprovider.go | 1 - 55 files changed, 4104 insertions(+), 1391 deletions(-) create mode 100644 LICENSE create mode 100644 container.go create mode 100644 di/container.go create mode 100644 di/container_test.go create mode 100644 di/dot.go create mode 100644 di/errors.go create mode 100644 di/internal/ditest/bar.go create mode 100644 di/internal/ditest/baz.go create mode 100644 di/internal/ditest/foo.go create mode 100644 di/internal/ditest/fooer_group.go create mode 100644 di/internal/ditest/full.go create mode 100644 di/internal/ditest/incorrect.go create mode 100644 di/internal/ditest/interfaces.go create mode 100644 di/internal/ditest/qux.go create mode 100644 di/internal/graphkv/directed_graph.go create mode 100644 di/internal/graphkv/directed_graph_test.go create mode 100644 di/internal/graphkv/edge.go create mode 100644 di/internal/graphkv/errors.go create mode 100644 di/internal/graphkv/graph.go create mode 100644 di/internal/graphkv/graph_test.go create mode 100644 di/internal/graphkv/graphkv.go create mode 100644 di/internal/graphkv/graphkv_test.go create mode 100644 di/internal/graphkv/key.go create mode 100644 di/internal/graphkv/output.go create mode 100644 di/internal/graphkv/output_test.go create mode 100644 di/internal/graphkv/sort.go create mode 100644 di/internal/graphkv/sort_test.go create mode 100644 di/internal/reflection/func.go create mode 100644 di/internal/reflection/iface.go create mode 100644 di/internal/reflection/reflection.go create mode 100644 di/invoker.go create mode 100644 di/key.go create mode 100644 di/options.go create mode 100644 di/panic.go create mode 100644 di/parameter.go create mode 100644 di/parameter_bag.go create mode 100644 di/parameter_bag_test.go create mode 100644 di/parameter_list.go create mode 100644 di/provider.go create mode 100644 di/provider_ctor.go create mode 100644 di/provider_embed.go create mode 100644 di/provider_group.go create mode 100644 di/provider_iface.go create mode 100644 di/provider_stub.go create mode 100644 di/singleton.go create mode 100644 graph.png create mode 100644 options.go create mode 100644 options_test.go diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d8c6682 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Maxim Bovtunov + +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. \ No newline at end of file diff --git a/README.md b/README.md index b17ed35..e8b4f55 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,433 @@ -# DI -Dependency injection for Go programming language. +## How will dependency injection help me? -Dependency injection is one form of the broader technique of inversion of control. It is used to increase modularity of the program and make it extensible. +Dependency injection is one form of the broader technique of inversion +of control. It is used to increase modularity of the program and make it +extensible. + +## Contents + +- [Installing](#installing) +- [Tutorial](#tutorial) + - [Providing](#providing) + - [Extraction](#extraction) + - [Invocation](#invocation) + - [Lazy-loading](#lazy-loading) + - [Interfaces](#interfaces) + - [Groups](#groups) +- [Advanced features](#advanced-features) + - [Named definitions](#named-definitions) + - [Optional parameters](#optional-parameters) + - [Parameter Bag](#parameter-bag) + - [Prototypes](#prototypes) + - [Cleanup](#cleanup) + - [Visualization](#visualization) +- [Contributing](#contributing) + +## Installing + +```shell +go get -u github.com/defval/inject/v2 +``` + +This library follows [SemVer](http://semver.org/) strictly. + +## Tutorial + +Let's learn to use Inject by example. We will code a simple application +that processes HTTP requests. + +The full tutorial code is available [here](./_tutorial/main.go) + +### Providing + +To start, we will need to create two fundamental types: `http.Server` +and `http.ServeMux`. Let's create a simple constructors that initialize +it: + +```go +// NewServer creates a http server with provided mux as handler. +func NewServer(mux *http.ServeMux) *http.Server { + return &http.Server{ + Handler: mux, + } +} + +// NewServeMux creates a new http serve mux. +func NewServeMux() *http.ServeMux { + return &http.ServeMux{} +} +``` + +> Supported constructor signature: +> +> ```go +> func([dep1, dep2, depN]) (result, [cleanup, error]) +> ``` + +Now let's teach a container to build these types. + +```go +container := inject.New( + // provide http server + inject.Provide(NewServer), + // provide http serve mux + inject.Provide(NewServeMux) +) +``` + +The function `inject.New()` parse our constructors, compile dependency +graph and return `*inject.Container` type for interaction. Container +panics if it could not compile. + +> I think that panic at the initialization of the application and not in +> runtime is usual. + +### Extraction + +We can extract the built server from the container. For this, define the +variable of extracted type and pass variable pointer to `Extract` +function. + +> If extracted type not found or the process of building instance cause +> error, `Extract` return error. + +If no error occurred, we can use the variable as if we had built it +yourself. + +```go +// declare type variable +var server *http.Server +// extracting +err := container.Extract(&server) +if err != nil { + // check extraction error +} + +server.ListenAndServe() +``` + +> Note that by default, the container creates instances as a singleton. +> But you can change this behaviour. See [Prototypes](#prototypes). + +### Invocation + +As an alternative to extraction we can use `Invoke()` function. It +resolves function dependencies and call the function. Invoke function +may return optional error. + +```go +// StartServer starts the server. +func StartServer(server *http.Server) error { + return server.ListenAndServe() +} + +container.Invoke(StartServer) +``` + +### Lazy-loading + +Result dependencies will be lazy-loaded. If no one requires a type from +the container it will not be constructed. + +### Interfaces + +Inject make possible to provide implementation as an interface. + +```go +// NewServer creates a http server with provided mux as handler. +func NewServer(handler http.Handler) *http.Server { + return &http.Server{ + Handler: handler, + } +} +``` + +For a container to know that as an implementation of `http.Handler` is +necessary to use, we use the option `inject.As()`. The arguments of this +option must be a pointer(s) to an interface like `new(Endpoint)`. + +> This syntax may seem strange, but I have not found a better way to +> specify the interface. + +Updated container initialization code: + +```go +container := inject.New( + // provide http server + inject.Provide(NewServer), + // provide http serve mux as http.Handler interface + inject.Provide(NewServeMux, inject.As(new(http.Handler))) +) +``` + +Now container uses provide `*http.ServeMux` as `http.Handler` in server +constructor. Using interfaces contributes to writing more testable code. + +### Groups + +Container automatically groups all implementations of interface to +`[]` group. For example, provide with +`inject.As(new(http.Handler)` automatically creates a group +`[]http.Handler`. + +Let's add some http controllers using this feature. Controllers have +typical behavior. It is registering routes. At first, will create an +interface for it. + +```go +// Controller is an interface that can register its routes. +type Controller interface { + RegisterRoutes(mux *http.ServeMux) +} +``` + +Now we will write controllers and implement `Controller` interface. + +##### OrderController + +```go +// OrderController is a http controller for orders. +type OrderController struct {} + +// NewOrderController creates a auth http controller. +func NewOrderController() *OrderController { + return &OrderController{} +} + +// RegisterRoutes is a Controller interface implementation. +func (a *OrderController) RegisterRoutes(mux *http.ServeMux) { + mux.HandleFunc("/orders", a.RetrieveOrders) +} + +// Retrieve loads orders and writes it to the writer. +func (a *OrderController) RetrieveOrders(writer http.ResponseWriter, request *http.Request) { + // implementation +} +``` + +##### UserController + +```go +// UserController is a http endpoint for a user. +type UserController struct {} + +// NewUserController creates a user http endpoint. +func NewUserController() *UserController { + return &UserController{} +} + +// RegisterRoutes is a Controller interface implementation. +func (e *UserController) RegisterRoutes(mux *http.ServeMux) { + mux.HandleFunc("/users", e.RetrieveUsers) +} + +// Retrieve loads users and writes it using the writer. +func (e *UserController) RetrieveUsers(writer http.ResponseWriter, request *http.Request) { + // implementation +} +``` + +Just like in the example with interfaces, we will use `inject.As()` +provide option. + +```go +container := inject.New( + inject.Provide(NewServer), // provide http server + inject.Provide(NewServeMux), // provide http serve mux + // endpoints + inject.Provide(NewOrderController, inject.As(new(Controller))), // provide order controller + inject.Provide(NewUserController, inject.As(new(Controller))), // provide user controller +) +``` + +Now, we can use `[]Controller` group in our mux. See updated code: + +```go +// NewServeMux creates a new http serve mux. +func NewServeMux(controllers []Controller) *http.ServeMux { + mux := &http.ServeMux{} + + for _, controller := range controllers { + controller.RegisterRoutes(mux) + } + + return mux +} +``` + +## Advanced features + +### Named definitions + +In some cases you have more than one instance of one type. For example +two instances of database: master - for writing, slave - for reading. + +First way is a wrapping types: + +```go +// MasterDatabase provide write database access. +type MasterDatabase struct { + *Database +} + +// SlaveDatabase provide read database access. +type SlaveDatabase struct { + *Database +} +``` + +Second way is a using named definitions with `inject.WithName()` provide +option: + +```go +// provide master database +inject.Provide(NewMasterDatabase, inject.WithName("master")) +// provide slave database +inject.Provide(NewSlaveDatabase, inject.WithName("slave")) +``` + +If you need to extract it from container use `inject.Name()` extract +option. + +```go +var db *Database +container.Extract(&db, inject.Name("master")) +``` + +If you need to provide named definition in other constructor use +`di.Parameter` with embedding. + +```go +// ServiceParameters +type ServiceParameters struct { + di.Parameter + + // use `di` tag for the container to know that field need to be injected. + MasterDatabase *Database `di:"master"` + SlaveDatabase *Database `di:"slave"` +} + +// NewService creates new service with provided parameters. +func NewService(parameters ServiceParameters) *Service { + return &Service{ + MasterDatabase: parameters.MasterDatabase, + SlaveDatabase: parameters.SlaveDatabase, + } +} +``` + +### Optional parameters + +Also `di.Parameter` provide ability to skip dependency if it not exists +in container. + +```go +// ServiceParameter +type ServiceParameter struct { + di.Parameter + + Logger *Logger `di:"optional"` +} +``` + +> Constructors that declare dependencies as optional must handle the +> case of those dependencies being absent. + +You can use naming and optional together. + +```go +// ServiceParameter +type ServiceParameter struct { + di.Parameter + + StdOutLogger *Logger `di:"stdout"` + FileLogger *Logger `di:"file,optional"` +} +``` + +### Parameter Bag + +If you need to specify some parameters on definition level you can use +`inject.ParameterBag` provide option. This is a `map[string]interface{}` +that transforms to `di.ParameterBag` type. + +```go +// Provide server with parameter bag +inject.Provide(NewServer, inject.ParameterBag{ + "addr": ":8080", +}) + +// NewServer create a server with provided parameter bag. Note: use di.ParameterBag type. +// Not inject.ParameterBag. +func NewServer(pb di.ParameterBag) *http.Server { + return &http.Server{ + Addr: pb.RequireString("addr"), + } +} +``` + +### Prototypes + +If you want to create a new instance on each extraction use +`inject.Prototype()` provide option. -## Examples ```go -type A struct { - Name string +inject.Provide(NewRequestContext, inject.Prototype()) +``` + +> todo: real use case + +### Cleanup + +If a provider creates a value that needs to be cleaned up, then it can +return a closure to clean up the resource. + +```go +func NewFile(log Logger, path Path) (*os.File, func(), error) { + f, err := os.Open(string(path)) + if err != nil { + return nil, nil, err + } + cleanup := func() { + if err := f.Close(); err != nil { + log.Log(err) + } + } + return f, cleanup, nil } +``` + +After `container.Cleanup()` call, it iterate over instances and call +cleanup function if it exists. -func NewA() *A { - r := rand.New(rand.NewSource(time.Now().UnixNano())) - name := "A-" + strconv.Itoa(r.Int()) - return &A{Name: ls} +```go +container := inject.New( + // ... + inject.Provide(NewFile), +) + +// do something +container.Cleanup() // file was closed +``` + +> Cleanup now work incorrectly with prototype providers. + +## Visualization + +Dependency graph may be presented via +([Graphviz](https://www.graphviz.org/)). For it, load string +representation: + +```go +var graph *di.Graph +if err = container.Extract(&graph); err != nil { + // handle err } -services := NewServiceCollection() -services.AddSingleton(NewA) -serviceProvider := services.Build() +dotGraph := graph.String() // use string representation +``` + +And paste it to graphviz online tool: -var env *A -_ = serviceProvider.GetService(&env) // used -``` \ No newline at end of file + \ No newline at end of file diff --git a/container.go b/container.go new file mode 100644 index 0000000..61d5de4 --- /dev/null +++ b/container.go @@ -0,0 +1,65 @@ +package dependencyinjection + +import ( + "github.com/yoyofxteam/dependencyinjection/di" +) + +// New creates a new container with provided options. +func New(options ...Option) *Container { + var c = &Container{ + container: di.New(), + } + // apply options. + for _, opt := range options { + opt.apply(c) + } + c.compile() + return c +} + +// Container is a dependency injection container. +type Container struct { + providers []provide + container *di.Container +} + +// Extract populates given target pointer with type instance provided in the container. +// +// var server *http.Server +// if err = container.Extract(&server); err != nil { +// // extract failed +// } +// +// If the target type does not exist in a container or instance type building failed, Extract() returns an error. +// Use ExtractOption for modifying the behavior of this function. +func (c *Container) Extract(target interface{}, options ...ExtractOption) (err error) { + var params = di.ExtractParams{} + // apply extract options + for _, opt := range options { + opt.apply(¶ms) + } + return c.container.Extract(target, params) +} + +// Invoke invokes custom function. Dependencies of function will be resolved via container. +func (c *Container) Invoke(fn interface{}) error { + return c.container.Invoke(fn) +} + +// Cleanup cleanup container. +func (c *Container) Cleanup() { + c.container.Cleanup() +} + +func (c *Container) compile() { + for _, po := range c.providers { + c.container.Provide(po.provider, po.params) + } + c.container.Compile() + return +} + +type provide struct { + provider interface{} + params di.ProvideParams +} diff --git a/container_test.go b/container_test.go index c2371d0..e08ce83 100644 --- a/container_test.go +++ b/container_test.go @@ -1,1363 +1,60 @@ package dependencyinjection import ( - "errors" "fmt" - "io" + "github.com/stretchr/testify/require" + "net" "net/http" - "os" "testing" - - "github.com/stretchr/testify/require" - - "github.com/goava/di" ) -func init() { - di.SetTracer(di.StdTracer{}) -} - -func TestContainer_Provide(t *testing.T) { - t.Run("simple constructor", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NoError(t, c.Provide(func() *http.Server { return &http.Server{} })) - }) - - t.Run("constructor with cleanup function", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NoError(t, c.Provide(func() (*http.Server, func()) { - return &http.Server{}, func() {} - })) - }) - - t.Run("constructor with cleanup and error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NoError(t, c.Provide(func() (*http.Server, func(), error) { - return &http.Server{}, func() {}, nil - })) - }) - - t.Run("provide string cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - err = c.Provide("string") - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": invalid constructor signature, got string") - }) - - t.Run("provide nil cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - err = c.Provide(nil) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": invalid constructor signature, got nil") - }) - - t.Run("provide struct pointer cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - err = c.Provide(&http.Server{}) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": invalid constructor signature, got *http.Server") - }) - - t.Run("provide constructor without result cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - err = c.Provide(func() {}) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": invalid constructor signature, got func()") - }) - - t.Run("provide constructor with many resultant types cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - ctor := func() (*http.Server, *http.ServeMux, error) { - return nil, nil, nil - } - err = c.Provide(ctor) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": invalid constructor signature, got func() (*http.Server, *http.ServeMux, error)") - }) - - t.Run("provide constructor with incorrect result error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - ctor := func() (*http.Server, *http.ServeMux) { - return nil, nil - } - err = c.Provide(ctor) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), "invalid constructor signature, got func() (*http.Server, *http.ServeMux)") - }) - - t.Run("provide duplicate not cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - ctor := func() *http.Server { return nil } - require.NoError(t, c.Provide(ctor)) - require.NoError(t, c.Provide(ctor)) - }) - - t.Run("provide as not implemented interface cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - // http server not implement io.Reader interface - err = c.Provide(func() *http.Server { return nil }, di.As(new(io.Reader))) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": *http.Server not implement io.Reader") - }) - - t.Run("provide type as several interfaces", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - file := &os.File{} - require.NoError(t, c.Provide(func() *os.File { return file }, di.As(new(io.Closer), new(io.ReadCloser)))) - var closer io.Closer - var readCloser io.ReadCloser - require.NoError(t, c.Resolve(&closer)) - require.NoError(t, c.Resolve(&readCloser)) - require.Equal(t, fmt.Sprintf("%p", closer), fmt.Sprintf("%p", file)) - require.Equal(t, fmt.Sprintf("%p", readCloser), fmt.Sprintf("%p", file)) - }) - - t.Run("using not interface type in di.As() cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - err = c.Provide(func() *http.Server { return nil }, di.As(&http.Server{})) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": *http.Server: not a pointer to interface") - }) - - t.Run("using nil type in di.As() cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - err = c.Provide(func() *http.Server { return &http.Server{} }, di.As(nil)) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": nil: not a pointer to interface") - }) -} - -func TestContainer_ProvideValue(t *testing.T) { - t.Run("provide nil value cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - err = c.ProvideValue(nil) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), "invalid value, got nil") - }) - - t.Run("provide and resolve value", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - mux := &http.ServeMux{} - err = c.ProvideValue(mux, di.As(new(http.Handler))) - require.NoError(t, err) - err = c.Provide(func(handler http.Handler) *http.Server { - return &http.Server{ - Handler: handler, - } - }) - require.NoError(t, err) - var server *http.Server - err = c.Resolve(&server) - require.NoError(t, err) - require.Equal(t, fmt.Sprintf("%p", mux), fmt.Sprintf("%p", server.Handler)) - }) - - t.Run("provide values by option", func(t *testing.T) { - mux := &http.ServeMux{} - c, err := di.New( - di.ProvideValue(mux), - ) - require.NoError(t, err) - require.NotNil(t, c) - var result *http.ServeMux - err = c.Resolve(&result) - require.NoError(t, err) - require.Equal(t, fmt.Sprintf("%p", mux), fmt.Sprintf("%p", result)) - }) - - t.Run("provide nil value by option", func(t *testing.T) { - c, err := di.New( - di.ProvideValue(nil), - ) - require.Error(t, err) - require.Nil(t, c) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), "invalid value, got nil") - }) -} - -func TestContainer_Resolve(t *testing.T) { - t.Run("resolve into nil cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - err = c.Resolve(nil) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": target must be a pointer, got nil") - }) - - t.Run("resolve into struct{} cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - err = c.Resolve(struct{}{}) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": target must be a pointer, got struct {}") - }) - - t.Run("resolve into string cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - err = c.Resolve("string") - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": target must be a pointer, got string") - }) - - t.Run("resolve with failed build", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - err = c.Provide(func() (*http.Server, error) { - return &http.Server{}, fmt.Errorf("server build failed") - }) - require.NoError(t, err) - var server *http.Server - err = c.Resolve(&server) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": *http.Server: server build failed") - }) - - t.Run("resolve with failed dependency build", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - err = c.Provide(func() (*http.Server, error) { - return &http.Server{}, fmt.Errorf("server build failed") - }) - require.NoError(t, err) - err = c.Provide(func(server *http.Server) string { - return "string" - }) - require.NoError(t, err) - var s string - err = c.Resolve(&s) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": *http.Server: server build failed") - }) - - t.Run("resolve cleanup error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - called := false - cleanup := func() { - called = true - } - require.NoError(t, c.Provide(func() (*http.Server, func(), error) { - return &http.Server{}, cleanup, nil - })) - var server *http.Server - require.NoError(t, c.Resolve(&server)) - c.Cleanup() - require.True(t, called) - }) - - t.Run("resolve returns type that was created in constructor", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - server := &http.Server{} - require.NoError(t, c.Provide(func() *http.Server { return server })) - var extracted *http.Server - require.NoError(t, c.Resolve(&extracted)) - require.Equal(t, fmt.Sprintf("%p", server), fmt.Sprintf("%p", extracted)) - }) - - t.Run("resolve same pointer on each resolve", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - require.NoError(t, c.Provide(func() *http.Server { return &http.Server{} })) - var server1 *http.Server - require.NoError(t, c.Resolve(&server1)) - var server2 *http.Server - require.NoError(t, c.Resolve(&server2)) - require.Equal(t, fmt.Sprintf("%p", server1), fmt.Sprintf("%p", server2)) - }) - - t.Run("resolve not existing dependency cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - err = c.Provide(func(handler http.Handler) *http.Server { return &http.Server{} }) - require.NoError(t, err) - var server *http.Server - err = c.Resolve(&server) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), "*http.Server: type http.Handler not exists in the container") - }) - - t.Run("resolve not existing type cause error", func(t *testing.T) { - c, err := di.New() - require.NotNil(t, c) - require.NoError(t, err) - err = c.Resolve(&http.Server{}) - require.Error(t, err) - require.True(t, errors.Is(err, di.ErrTypeNotExists)) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": type http.Server not exists in the container") - }) - - t.Run("resolve functions", func(t *testing.T) { - var result []string - fn1 := func() { result = append(result, "fn1") } - fn2 := func() { result = append(result, "fn2") } - fn3 := func() { result = append(result, "fn3") } - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - type MyFunc func() - require.NoError(t, c.Provide(func() MyFunc { return fn1 })) - require.NoError(t, c.Provide(func() MyFunc { return fn2 })) - require.NoError(t, c.Provide(func() MyFunc { return fn3 })) - var funcs []MyFunc - require.NoError(t, c.Resolve(&funcs)) - require.Len(t, funcs, 3) - for _, fn := range funcs { - fn() - } - require.Equal(t, []string{"fn1", "fn2", "fn3"}, result) - }) - - t.Run("container provided by default", func(t *testing.T) { - var container *di.Container - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - require.NoError(t, c.Resolve(&container)) - require.Equal(t, fmt.Sprintf("%p", c), fmt.Sprintf("%p", container)) - }) - - t.Run("cycle cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - // bool -> int32 -> int64 -> bool - err = c.Provide(func(int32) bool { return true }) - require.NoError(t, err) - err = c.Provide(func(int64) int32 { return 0 }) - require.NoError(t, err) - err = c.Provide(func(bool) int64 { return 0 }) - require.NoError(t, err) - var b bool - err = c.Resolve(&b) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": cycle detected") // todo: improve message - }) - - t.Run("resolve not existing dependency type cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - require.NoError(t, c.Provide(func(int) int32 { return 0 })) - var i int32 - err = c.Resolve(&i) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": int32: type int not exists in the container") - }) - - t.Run("resolve correct argument", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - mux := &http.ServeMux{} - require.NoError(t, c.Provide(func() *http.ServeMux { return mux })) - require.NoError(t, c.Provide(func(mux *http.ServeMux) *http.Server { - return &http.Server{Handler: mux} - })) - var server *http.Server - require.NoError(t, c.Resolve(&server)) - require.Equal(t, fmt.Sprintf("%p", mux), fmt.Sprintf("%p", server.Handler)) - }) -} - -func TestContainer_Apply(t *testing.T) { - t.Run("apply applies container options with error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - err = c.Apply( - di.Provide(func() *http.Server { return &http.Server{} }, di.As(new(io.Closer))), - di.Provide(func() *os.File { return &os.File{} }, di.As(new(io.Closer))), - ) - require.NoError(t, err) - var closer io.Closer - err = c.Resolve(&closer) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": multiple definitions of io.Closer, maybe you need to use group type: []io.Closer") - }) -} - -func TestContainer_Interfaces(t *testing.T) { - t.Run("resolve interface with several implementations cause error", func(t *testing.T) { - c, err := di.New( - di.Provide(func() *http.Server { return &http.Server{} }, di.As(new(io.Closer))), - di.Provide(func() *os.File { return &os.File{} }, di.As(new(io.Closer))), - ) - require.NoError(t, err) - var closer io.Closer - err = c.Resolve(&closer) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": multiple definitions of io.Closer, maybe you need to use group type: []io.Closer") - }) - - t.Run("resolve constructor interface argument", func(t *testing.T) { - mux := &http.ServeMux{} - c, err := di.New( - di.Provide(func() *http.ServeMux { return mux }, di.As(new(http.Handler))), - di.Provide(func(handler http.Handler) *http.Server { return &http.Server{Handler: handler} }), - ) - require.NoError(t, err) - var handler http.Handler - err = c.Resolve(&handler) - require.NoError(t, err) - var server *http.Server - err = c.Resolve(&server) - require.NoError(t, err) - require.Equal(t, fmt.Sprintf("%p", mux), fmt.Sprintf("%p", server.Handler)) - }) - - t.Run("resolve not existing unnamed definition with named", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NoError(t, c.Provide(http.NewServeMux, di.WithName("two"))) - require.NoError(t, c.Provide(http.NewServeMux, di.WithName("three"))) - var mux *http.ServeMux - err = c.Resolve(&mux) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": multiple definitions of *http.ServeMux, maybe you need to use group type: []*http.ServeMux") - }) - - t.Run("resolve same pointer on resolve", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - require.NoError(t, c.Provide(func() *http.ServeMux { return &http.ServeMux{} }, di.As(new(http.Handler)))) - var server *http.ServeMux - require.NoError(t, c.Resolve(&server)) - var handler http.Handler - require.NoError(t, c.Resolve(&handler)) - require.Equal(t, fmt.Sprintf("%p", server), fmt.Sprintf("%p", handler)) - }) -} - -func TestContainer_Groups(t *testing.T) { - t.Run("resolve multiple type instances as slice of type", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - conn1 := &net.TCPConn{} - conn2 := &net.TCPConn{} - require.NoError(t, c.Provide(func() *net.TCPConn { return conn1 })) - require.NoError(t, c.Provide(func() *net.TCPConn { return conn2 })) - var conns []*net.TCPConn - require.NoError(t, c.Resolve(&conns)) - require.Len(t, conns, 2) - require.Equal(t, fmt.Sprintf("%p", conn1), fmt.Sprintf("%p", conns[0])) - require.Equal(t, fmt.Sprintf("%p", conn2), fmt.Sprintf("%p", conns[1])) - }) - - t.Run("resolve not specific type of group cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - conn1 := &net.TCPConn{} - conn2 := &net.TCPConn{} - require.NoError(t, c.Provide(func() *net.TCPConn { return conn1 })) - require.NoError(t, c.Provide(func() *net.TCPConn { return conn2 })) - var conn *net.TCPConn - err = c.Resolve(&conn) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": multiple definitions of *net.TCPConn, maybe you need to use group type: []*net.TCPConn") - }) - - t.Run("resolve group of interface", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - server := &http.Server{} - file := &os.File{} - require.NoError(t, c.Provide(func() *http.Server { return server }, di.As(new(io.Closer)))) - require.NoError(t, c.Provide(func() *os.File { return file }, di.As(new(io.Closer)))) - var closers []io.Closer - require.NoError(t, c.Resolve(&closers)) - require.Len(t, closers, 2) - require.Equal(t, fmt.Sprintf("%p", server), fmt.Sprintf("%p", closers[0])) - require.Equal(t, fmt.Sprintf("%p", file), fmt.Sprintf("%p", closers[1])) - }) - - t.Run("group updates on provide", func(t *testing.T) { - var result []string - fn1 := func() { result = append(result, "fn1") } - fn2 := func() { result = append(result, "fn2") } - fn3 := func() { result = append(result, "fn3") } - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - type MyFunc func() - var funcs []MyFunc - require.NoError(t, c.Provide(func() MyFunc { return fn1 })) - require.NoError(t, c.Resolve(&funcs)) - require.Len(t, funcs, 1) - require.NoError(t, c.Provide(func() MyFunc { return fn2 })) - require.NoError(t, c.Resolve(&funcs)) - require.Len(t, funcs, 2) - require.NoError(t, c.Provide(func() MyFunc { return fn3 })) - require.NoError(t, c.Resolve(&funcs)) - require.Len(t, funcs, 3) - }) +func TestContainer(t *testing.T) { - t.Run("resolve one interface from group of type", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - conn1 := &net.TCPConn{} - conn2 := &net.TCPConn{} - require.NoError(t, c.Provide(func() *net.TCPConn { return conn1 })) - require.NoError(t, c.Provide(func() *net.TCPConn { return conn2 }, di.As(new(net.Conn)))) - var conn net.Conn - require.NoError(t, c.Resolve(&conn)) - require.Equal(t, fmt.Sprintf("%p", conn), fmt.Sprintf("%p", conn)) - }) -} - -func TestContainer_Iterate(t *testing.T) { - t.Run("iterate over nil causes error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - err = c.Iterate(nil, func(tags di.Tags, loader di.ValueFunc) error { - return nil - }) - require.EqualError(t, err, "target must be a pointer, got nil") - }) - t.Run("iterate over struct causes error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - err = c.Iterate(http.ServeMux{}, func(tags di.Tags, loader di.ValueFunc) error { - return nil - }) - require.EqualError(t, err, "target must be a pointer, got http.ServeMux") - }) - t.Run("iterate over struct causes error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - require.NoError(t, c.Provide(func() http.ServeMux { return http.ServeMux{} })) - err = c.Iterate(&http.ServeMux{}, func(tags di.Tags, loader di.ValueFunc) error { - return nil - }) - require.EqualError(t, err, "iteration can be used with groups only") - }) - t.Run("iterates over instances", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - conn1 := &net.TCPConn{} - conn2 := &net.TCPConn{} - require.NoError(t, c.Provide(func() *net.TCPConn { return conn1 })) - require.NoError(t, c.Provide(func() *net.TCPConn { return conn2 })) - var iterates []*net.TCPConn - var conn []*net.TCPConn - iterFn := func(tags di.Tags, loader di.ValueFunc) error { - i, err := loader() - if err != nil { - return err - } - iterates = append(iterates, i.(*net.TCPConn)) - return nil - } - err = c.Iterate(&conn, iterFn) - require.NoError(t, err) - require.Len(t, iterates, 2) - require.Equal(t, iterates[0], conn1) - require.Equal(t, iterates[1], conn2) - }) - - t.Run("iterates over tagged instances", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - conn1 := &net.TCPConn{} - conn2 := &net.TCPConn{} - require.NoError(t, c.Provide(func() *net.TCPConn { return conn1 }, di.Tags{"conn": "tcp1"})) - require.NoError(t, c.Provide(func() *net.TCPConn { return conn2 }, di.Tags{"conn": "tcp2"})) - require.NoError(t, c.Provide(func() *net.TCPConn { return &net.TCPConn{} })) - var iterates []*net.TCPConn - var all []di.Tags - var conn []*net.TCPConn - iterFn := func(tags di.Tags, loader di.ValueFunc) error { - all = append(all, tags) - i, err := loader() - if err != nil { - return err - } - iterates = append(iterates, i.(*net.TCPConn)) - return nil - } - err = c.Iterate(&conn, iterFn, di.Tags{"conn": "*"}) - require.NoError(t, err) - require.Len(t, iterates, 2) - require.Equal(t, conn1, iterates[0]) - require.Equal(t, conn2, iterates[1]) - require.Equal(t, []di.Tags{ - {"conn": "tcp1"}, - {"conn": "tcp2"}, - }, all) - }) - - t.Run("iterates over instances with errors", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - conn1 := &net.TCPConn{} - conn2 := &net.TCPConn{} - require.NoError(t, c.Provide(func() *net.TCPConn { return conn1 })) - require.NoError(t, c.Provide(func() (*net.TCPConn, error) { return conn2, fmt.Errorf("tcp conn 2 error") })) - var iterates []*net.TCPConn - var conn []*net.TCPConn - iterFn := func(tags di.Tags, loader di.ValueFunc) error { - i, err := loader() - if err != nil { - return err - } - iterates = append(iterates, i.(*net.TCPConn)) - return nil - } - err = c.Iterate(&conn, iterFn) - require.EqualError(t, err, "[]*net.TCPConn with index 1 failed: tcp conn 2 error") - }) -} - -func TestContainer_Tags(t *testing.T) { - t.Run("resolve named definition", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - first := &http.Server{} - second := &http.Server{} - err = c.Provide(func() *http.Server { return first }, di.WithName("first")) - require.NoError(t, err) - err = c.Provide(func() *http.Server { return second }, di.WithName("second")) - require.NoError(t, err) - var extracted *http.Server - err = c.Resolve(&extracted) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": multiple definitions of *http.Server, maybe you need to use group type: []*http.Server") - err = c.Resolve(&extracted, di.Name("first")) - require.NoError(t, err) - require.Equal(t, fmt.Sprintf("%p", first), fmt.Sprintf("%p", extracted)) - err = c.Resolve(&extracted, di.Name("second")) - require.NoError(t, err) - require.Equal(t, fmt.Sprintf("%p", second), fmt.Sprintf("%p", extracted)) - }) - - t.Run("resolve single instance of group without specifying tags cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - require.NoError(t, c.Provide(http.NewServeMux, di.WithName("first"))) - var mux *http.ServeMux - require.NoError(t, c.Resolve(&mux)) - require.NoError(t, c.Provide(http.NewServeMux, di.WithName("second"))) - err = c.Resolve(&mux) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": multiple definitions of *http.ServeMux, maybe you need to use group type: []*http.ServeMux") - }) + var HTTPBundle = Bundle( + Provide(ProvideAddr("0.0.0.0", "8080")), + Provide(NewMux, As(new(http.Handler))), + Provide(NewHTTPServer, Prototype(), WithName("server")), + ) - t.Run("resolve not found by tags instance cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - require.NoError(t, c.Provide(http.NewServeMux, di.WithName("first"))) - require.NoError(t, c.Provide(http.NewServeMux, di.WithName("second"))) - var mux *http.ServeMux - err = c.Resolve(&mux, di.Name("unknown")) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": type *http.ServeMux[name:unknown] not exists") - }) + c := New(HTTPBundle) - t.Run("provide duplication of named definition", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NotNil(t, c) - require.NoError(t, c.Provide(http.NewServeMux, di.WithName("first"))) - err = c.Provide(http.NewServeMux, di.WithName("first")) - require.NoError(t, err) - }) + var server1 *http.Server + err := c.Extract(&server1, Name("server")) + require.NoError(t, err) - t.Run("resolve existing unnamed definition with named", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NoError(t, c.Provide(http.NewServeMux)) - require.NoError(t, c.Provide(http.NewServeMux, di.WithName("two"))) - require.NoError(t, c.Provide(http.NewServeMux, di.WithName("three"))) - var mux *http.ServeMux - err = c.Resolve(&mux) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), "multiple definitions of *http.ServeMux, maybe you need to use group type: []*http.ServeMux") - require.NoError(t, c.Resolve(&mux, di.Name("two"))) - require.NoError(t, c.Resolve(&mux, di.Name("three"))) - }) + var server2 *http.Server + err = c.Extract(&server2, Name("server")) + require.NoError(t, err) - t.Run("resolve instances with same tag", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NoError(t, c.Provide(http.NewServeMux)) - require.NoError(t, c.Provide(http.NewServeMux, di.Tags{"tag": "the_same"})) - require.NoError(t, c.Provide(http.NewServeMux, di.Tags{"tag": "the_same"})) - var muxs []*http.ServeMux - err = c.Resolve(&muxs, di.Tags{"tag": "the_same"}) - require.NoError(t, err) - require.Len(t, muxs, 2) - }) - - t.Run("resolve all instances with tag", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NoError(t, c.Provide(http.NewServeMux)) - require.NoError(t, c.Provide(http.NewServeMux, di.Tags{"server": "one"})) - require.NoError(t, c.Provide(http.NewServeMux, di.Tags{"server": "two"})) - var muxs []*http.ServeMux - err = c.Resolve(&muxs, di.Tags{"server": "*"}) - require.NoError(t, err) - require.Len(t, muxs, 2) - }) - - t.Run("resolve all instances with several tags", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NoError(t, c.Provide(http.NewServeMux)) - require.NoError(t, c.Provide(http.NewServeMux, di.Tags{"server": "one"})) - require.NoError(t, c.Provide(http.NewServeMux, di.Tags{"server": "one", "http": "one"})) - require.NoError(t, c.Provide(http.NewServeMux, di.Tags{"server": "two", "http": "two"})) - var muxs []*http.ServeMux - err = c.Resolve(&muxs, di.Tags{"server": "*", "http": "*"}) - require.NoError(t, err) - require.Len(t, muxs, 2) - }) - - t.Run("provide type with tags", func(t *testing.T) { - type Server struct { - di.Tags `http:"true" server:"true"` - } - var s *Server - _, err := di.New( - di.Provide(func() *Server { return &Server{} }), - di.Resolve(&s, di.Tags{"http": "true", "server": "true"}), - ) - require.NoError(t, err) - }) + err = c.Invoke(PrintAddr) + require.NoError(t, err) } -func TestContainer_Group(t *testing.T) { - t.Run("resolve group argument", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - server := &http.Server{} - file := &os.File{} - require.NoError(t, c.Provide(func() *http.Server { return server }, di.As(new(io.Closer)))) - require.NoError(t, c.Provide(func() *os.File { return file }, di.As(new(io.Closer)))) - type Closers []io.Closer - require.NoError(t, c.Provide(func(closers []io.Closer) Closers { return closers })) - var closers Closers - require.NoError(t, c.Resolve(&closers)) - require.Equal(t, fmt.Sprintf("%p", server), fmt.Sprintf("%p", closers[0])) - require.Equal(t, fmt.Sprintf("%p", file), fmt.Sprintf("%p", closers[1])) - }) +// Addr +type Addr string - t.Run("incorrect signature", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - err = c.Invoke(func() *http.Server { return &http.Server{} }) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": invalid invocation signature, got func() *http.Server") - }) +// ProvideAddr +func ProvideAddr(host string, port string) func() Addr { + return func() Addr { + return Addr(net.JoinHostPort(host, port)) + } } -func TestContainer_Invoke(t *testing.T) { - t.Run("invoke nil", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - err = c.Invoke(nil) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": invalid invocation signature, got nil") - }) - - t.Run("invoke non function type", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - err = c.Invoke(1) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": invalid invocation signature, got int") - }) - - t.Run("invoke invalid function", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - err = c.Invoke(func() *http.Server { return &http.Server{} }) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": invalid invocation signature, got func() *http.Server") - }) - - t.Run("invocation function with not provided dependency cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - err = c.Invoke(func(server *http.Server) {}) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": type *http.Server not exists in the container") - }) - - t.Run("invocation function with dependency that can't be constructed", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - err = c.Provide(func() (*http.Server, error) { return nil, fmt.Errorf("server error") }) - require.NoError(t, err) - err = c.Invoke(func(server *http.Server) {}) - require.EqualError(t, err, "*http.Server: server error") - }) - - t.Run("invoke with nil error must be called", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - var invokeCalled bool - err = c.Invoke(func() error { - invokeCalled = true - return nil - }) - require.NoError(t, err) - require.True(t, invokeCalled) - }) - - t.Run("resolve dependencies in invoke", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - server := &http.Server{} - called := false - require.NoError(t, c.Provide(func() *http.Server { return server })) - err = c.Invoke(func(in *http.Server) { - called = true - require.Equal(t, fmt.Sprintf("%p", server), fmt.Sprintf("%p", in)) - }) - require.NoError(t, err) - require.True(t, called) - }) - - t.Run("invoke return error as is", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - err = c.Invoke(func() error { return fmt.Errorf("invoke error") }) - require.EqualError(t, err, "invoke error") - }) - - t.Run("cycle cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - // bool -> int32 -> int64 -> bool - err = c.Provide(func(int32) bool { return true }) - require.NoError(t, err) - err = c.Provide(func(int64) int32 { return 0 }) - require.NoError(t, err) - err = c.Provide(func(bool) int64 { return 0 }) - require.NoError(t, err) - err = c.Invoke(func(bool) {}) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": cycle detected") // todo: improve message - }) +// NewHTTPServer +func NewHTTPServer(addr Addr, handler http.Handler) *http.Server { + return &http.Server{ + Addr: string(addr), + Handler: handler, + } } -func TestContainer_Has(t *testing.T) { - t.Run("exists nil returns false", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - has, err := c.Has(nil) - require.EqualError(t, err, "target must be a pointer, got nil") - require.False(t, has) - }) - - t.Run("exists return true if type exists", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NoError(t, c.Provide(func() *http.Server { return &http.Server{} })) - var server *http.Server - has, err := c.Has(&server) - require.NoError(t, err) - require.True(t, has) - }) - - t.Run("exists return false if type not exists", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - var server *http.Server - has, err := c.Has(&server) - require.NoError(t, err) - require.False(t, has) - }) - - t.Run("exists interface", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NoError(t, c.Provide(func() *http.Server { return &http.Server{} }, di.As(new(io.Closer)))) - var server io.Closer - has, err := c.Has(&server) - require.NoError(t, err) - require.True(t, has) - }) - - t.Run("exists named provider", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - err = c.Provide(func() *http.Server { return &http.Server{} }, di.Tags{"name": "server"}) - require.NoError(t, err) - var server *http.Server - has, err := c.Has(&server, di.Tags{"name": "server"}) - require.NoError(t, err) - require.True(t, has) - }) - - t.Run("type exists but no possible to build returns true", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - require.NoError(t, c.Provide(func(b bool) *http.Server { return &http.Server{} })) - var server *http.Server - has, err := c.Has(&server) - require.EqualError(t, err, "*http.Server: type bool not exists in the container") - require.False(t, has) - }) +// NewMux +func NewMux() *http.ServeMux { + return &http.ServeMux{} } -func TestContainer_Inject(t *testing.T) { - t.Run("inject into provided struct pointer with di.Inject", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - type InjectableType struct { - di.Inject - Mux *http.ServeMux - } - mux := &http.ServeMux{} - require.NoError(t, c.Provide(func() *http.ServeMux { return mux })) - require.NoError(t, c.Provide(func() *InjectableType { return &InjectableType{} })) - var result *InjectableType - require.NoError(t, c.Resolve(&result)) - require.NotNil(t, result.Mux) - require.Equal(t, fmt.Sprintf("%p", mux), fmt.Sprintf("%p", result.Mux)) - }) - - t.Run("inject into provided struct value with di.Inject", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - type InjectableType struct { - di.Inject - Mux *http.ServeMux - } - mux := &http.ServeMux{} - require.NoError(t, c.Provide(func() *http.ServeMux { return mux })) - err = c.Provide(func() InjectableType { return InjectableType{} }) - require.NoError(t, err) - var it InjectableType - err = c.Resolve(&it) - require.NoError(t, err) - require.Equal(t, fmt.Sprintf("%p", mux), fmt.Sprintf("%p", it.Mux)) - }) - - t.Run("constructor with injectable embed pointer", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - type InjectableType struct { - di.Inject - *http.ServeMux - } - mux := &http.ServeMux{} - require.NoError(t, c.Provide(func() *http.ServeMux { return mux })) - require.NoError(t, c.Provide(func() *InjectableType { return &InjectableType{} })) - var result *InjectableType - require.NoError(t, c.Resolve(&result)) - require.NotNil(t, result.ServeMux) - require.Equal(t, fmt.Sprintf("%p", mux), fmt.Sprintf("%p", result.ServeMux)) - }) - - t.Run("container resolve injectable parameter", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - type Parameters struct { - di.Inject - Server *http.Server - File *os.File - } - server := &http.Server{} - file := &os.File{} - require.NoError(t, c.Provide(func() *http.Server { return server })) - require.NoError(t, c.Provide(func() *os.File { return file })) - type Result struct { - server *http.Server - file *os.File - } - require.NoError(t, c.Provide(func(params Parameters) *Result { - return &Result{params.Server, params.File} - })) - var extracted *Result - require.NoError(t, c.Resolve(&extracted)) - require.Equal(t, fmt.Sprintf("%p", server), fmt.Sprintf("%p", extracted.server)) - require.Equal(t, fmt.Sprintf("%p", file), fmt.Sprintf("%p", extracted.file)) - }) - - t.Run("not existing and optional field set to nil", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - type InjectableType struct { - di.Inject - Mux *http.ServeMux `optional:"true"` - } - require.NoError(t, c.Provide(func() *InjectableType { return &InjectableType{} })) - var result *InjectableType - require.NoError(t, c.Resolve(&result)) - require.Nil(t, result.Mux) - }) - - t.Run("nested injectable field resolved correctly", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - type NestedInjectableType struct { - di.Inject - Mux *http.ServeMux - } - type InjectableType struct { - di.Inject - Nested NestedInjectableType - } - mux := &http.ServeMux{} - require.NoError(t, c.Provide(func() *InjectableType { return &InjectableType{} })) - require.NoError(t, c.Provide(func() *http.ServeMux { return mux })) - var result *InjectableType - require.NoError(t, c.Resolve(&result)) - require.NotNil(t, result.Nested.Mux) - require.Equal(t, fmt.Sprintf("%p", mux), fmt.Sprintf("%p", result.Nested.Mux)) - var nit NestedInjectableType - require.NoError(t, c.Resolve(&nit)) - require.NotNil(t, nit) - require.Equal(t, fmt.Sprintf("%p", mux), fmt.Sprintf("%p", nit.Mux)) - }) - - t.Run("cycle in injectable fields cause error", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - type InjectableType struct { - di.Inject - String string - } - require.NoError(t, c.Provide(func() *InjectableType { return &InjectableType{} })) - require.NoError(t, c.Provide(func(t *InjectableType) string { return "" })) - var result *InjectableType - err = c.Resolve(&result) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": cycle detected") - }) - - t.Run("optional parameter may be nil", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - type Parameter struct { - di.Inject - Server *http.Server `optional:"true"` - } - type Result struct { - server *http.Server - } - require.NoError(t, c.Provide(func(params Parameter) *Result { return &Result{server: params.Server} })) - var extracted *Result - require.NoError(t, c.Resolve(&extracted)) - require.Nil(t, extracted.server) - }) - - t.Run("resolve group in params", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - - type Fn func() - type Params struct { - di.Inject - Handlers []Fn `optional:"true"` - } - require.NoError(t, c.Provide(func() Fn { return func() {} })) - require.NoError(t, c.Provide(func() Fn { return func() {} })) - require.NoError(t, c.Provide(func(params Params) bool { - return len(params.Handlers) == 2 - })) - var extracted bool - require.NoError(t, c.Resolve(&extracted)) - require.True(t, extracted) - }) - - t.Run("optional group may be nil", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - type Params struct { - di.Inject - Handlers []http.Handler `optional:"true"` - } - require.NoError(t, c.Provide(func(params Params) bool { - return params.Handlers == nil - })) - var extracted bool - require.NoError(t, c.Resolve(&extracted)) - require.True(t, extracted) - }) - - t.Run("skip private and skip tagged fields", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - type InjectableParameter struct { - di.Inject - private []http.Handler - Addrs []net.Addr `optional:"true"` - Skipped *http.ServeMux `skip:"true"` - } - type InjectableType struct { - di.Inject - private []http.Handler - Addrs []net.Addr `optional:"true"` - } - require.NoError(t, c.Provide(func(param InjectableParameter) bool { - return param.Addrs == nil - })) - require.NoError(t, c.Provide(func() *InjectableType { return &InjectableType{} })) - var extracted bool - require.NoError(t, c.Resolve(&extracted)) - require.True(t, extracted) - var result *InjectableType - require.NoError(t, c.Resolve(&result)) - - mux := http.NewServeMux() - p := InjectableParameter{Skipped: mux} - require.NoError(t, c.Resolve(&p)) - require.Equal(t, InjectableParameter{Skipped: mux}, p) - }) - - t.Run("resolving provided injectable as interface with dependency", func(t *testing.T) { - type InjectableType struct { - di.Inject - Server *http.Server - } - ctor := func() *InjectableType { - return &InjectableType{} - } - server := &http.Server{} - c, err := di.New( - di.Provide(func() *http.Server { return server }), - di.Provide(ctor, di.As(new(di.Interface))), - ) - require.NoError(t, err) - var b di.Interface - err = c.Resolve(&b) - require.NoError(t, err) - require.Equal(t, fmt.Sprintf("%p", server), fmt.Sprintf("%p", b.(*InjectableType).Server)) - }) - - t.Run("resolving provided injectable as interface without dependency cause error", func(t *testing.T) { - type InjectableType struct { - di.Inject - Server *http.Server - } - ctor := func() *InjectableType { - return &InjectableType{} - } - c, err := di.New( - di.Provide(ctor, di.As(new(di.Interface))), - ) - require.NoError(t, err) - var b di.Interface - err = c.Resolve(&b) - require.Error(t, err) - require.Contains(t, err.Error(), "container_test.go:") - require.Contains(t, err.Error(), ": di.Interface: type *http.Server not exists in the container") - }) - - t.Run("invoke with inject dependency struct", func(t *testing.T) { - type InjectableParam struct { - di.Inject - Mux *http.ServeMux - } - c, err := di.New() - require.NoError(t, err) - mux := http.NewServeMux() - require.NoError(t, c.Provide(func() *http.ServeMux { return mux })) - err = c.Invoke(func(params InjectableParam) { - require.Equal(t, fmt.Sprintf("%p", mux), fmt.Sprintf("%p", params.Mux)) - }) - require.NoError(t, err) - }) - - t.Run("invoke with inject dependency pointer", func(t *testing.T) { - type InjectableParam struct { - di.Inject - Mux *http.ServeMux - } - c, err := di.New() - require.NoError(t, err) - mux := http.NewServeMux() - require.NoError(t, c.Provide(func() *http.ServeMux { return mux })) - var ip *InjectableParam - err = c.Invoke(func(params *InjectableParam) { - ip = params - }) - require.NoError(t, err) - require.Equal(t, fmt.Sprintf("%p", mux), fmt.Sprintf("%p", ip.Mux)) - }) -} - -func TestContainer_Cleanup(t *testing.T) { - t.Run("called", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - var cleanupCalled bool - require.NoError(t, c.Provide(func() (*http.Server, func()) { - return &http.Server{}, func() { cleanupCalled = true } - })) - var extracted *http.Server - require.NoError(t, c.Resolve(&extracted)) - c.Cleanup() - require.True(t, cleanupCalled) - }) - - t.Run("correct order", func(t *testing.T) { - c, err := di.New() - require.NoError(t, err) - var cleanupCalls []string - require.NoError(t, c.Provide(func(handler http.Handler) (*http.Server, func()) { - return &http.Server{Handler: handler}, func() { cleanupCalls = append(cleanupCalls, "server") } - })) - require.NoError(t, c.Provide(func() (*http.ServeMux, func()) { - return &http.ServeMux{}, func() { cleanupCalls = append(cleanupCalls, "mux") } - }, di.As(new(http.Handler)))) - var server *http.Server - require.NoError(t, c.Resolve(&server)) - c.Cleanup() - require.Equal(t, []string{"server", "mux"}, cleanupCalls) - }) -} - -func TestContainer_AddParent(t *testing.T) { - t.Run("provide ancestor and resolve in child", func(t *testing.T) { - papaw, err := di.New() - require.NoError(t, err) - require.NotNil(t, papaw) - parent, err := di.New() - require.NoError(t, err) - require.NotNil(t, parent) - child, err := di.New() - require.NoError(t, err) - require.NotNil(t, child) - - require.NoError(t, parent.AddParent(papaw)) - require.NoError(t, child.AddParent(parent)) - - conn1 := &net.TCPConn{} - require.NoError(t, papaw.Provide(func() *net.TCPConn { return conn1 })) - - var conn *net.TCPConn - require.NoError(t, child.Resolve(&conn)) - require.Equal(t, fmt.Sprintf("%p", conn1), fmt.Sprintf("%p", conn)) - }) - - t.Run("resolve multiple type instances across ancestors", func(t *testing.T) { - papaw, err := di.New() - require.NoError(t, err) - require.NotNil(t, papaw) - parent, err := di.New() - require.NoError(t, err) - require.NotNil(t, parent) - child, err := di.New() - require.NoError(t, err) - require.NotNil(t, child) - - require.NoError(t, parent.AddParent(papaw)) - require.NoError(t, child.AddParent(parent)) - - conn1 := &net.TCPConn{} - conn2 := &net.TCPConn{} - conn3 := &net.TCPConn{} - require.NoError(t, papaw.Provide(func() *net.TCPConn { return conn1 })) - require.NoError(t, parent.Provide(func() *net.TCPConn { return conn2 })) - require.NoError(t, child.Provide(func() *net.TCPConn { return conn3 })) - - var conns []*net.TCPConn - require.NoError(t, child.Resolve(&conns)) - require.Len(t, conns, 3) - require.Equal(t, fmt.Sprintf("%p", conn1), fmt.Sprintf("%p", conns[0])) - require.Equal(t, fmt.Sprintf("%p", conn2), fmt.Sprintf("%p", conns[1])) - require.Equal(t, fmt.Sprintf("%p", conn3), fmt.Sprintf("%p", conns[2])) - }) - - t.Run("add parent errors", func(t *testing.T) { - parent, err := di.New() - require.NoError(t, err) - require.NotNil(t, parent) - err = parent.AddParent(parent) - require.Contains(t, err.Error(), "self cycle detected") - child, err := di.New() - require.NoError(t, err) - require.NotNil(t, child) - err = child.AddParent(parent) - require.NoError(t, err) - err = parent.AddParent(child) - require.Contains(t, err.Error(), "cycle detected") - err = child.AddParent(parent) - require.Contains(t, err.Error(), "parent already chained") - papaw, err := di.New() - require.NoError(t, err) - require.NotNil(t, papaw) - err = parent.AddParent(papaw) - err = papaw.AddParent(child) - require.Contains(t, err.Error(), "cycle detected") - }) - +// PrintAddr +func PrintAddr(addr Addr) { + fmt.Println(addr) } diff --git a/default_serviceprovider.go b/default_serviceprovider.go index 7f3c97e..7e164f4 100644 --- a/default_serviceprovider.go +++ b/default_serviceprovider.go @@ -1,34 +1,31 @@ package dependencyinjection import ( - "github.com/goava/di" - "unsafe" + "github.com/yoyofxteam/dependencyinjection/di" ) type DefaultServiceProvider struct { - container *di.Container + container *Container } func (d DefaultServiceProvider) GetService(refObject interface{}) (err error) { - err = d.container.Resolve(refObject) + err = d.container.Extract(refObject) return err } func (d DefaultServiceProvider) GetServiceByName(refObject interface{}, name string) (err error) { - err = d.container.Resolve(refObject, di.Name(name)) - return err -} + err = d.container.Extract(refObject, Name(name)) -func (d DefaultServiceProvider) GetServiceByTags(refObject interface{}, tags map[string]string) (err error) { - p := unsafe.Pointer(&tags) - var tag di.Tags - tag = *(*di.Tags)(p) - err = d.container.Resolve(refObject, tag) return err } func (d DefaultServiceProvider) GetGraph() string { - return "" + var graph *di.Graph + if err := d.container.Extract(&graph); err != nil { + // handle err + } + + return graph.String() // use string representation } func (d DefaultServiceProvider) InvokeService(fn interface{}) error { diff --git a/default_serviceprovider_factory.go b/default_serviceprovider_factory.go index 0e942b0..5d3cfd7 100644 --- a/default_serviceprovider_factory.go +++ b/default_serviceprovider_factory.go @@ -1,30 +1,26 @@ package dependencyinjection -import ( - "github.com/goava/di" -) - func (sc ServiceCollection) Build() IServiceProvider { - var providers []di.Option + var providers []Option for _, desc := range sc.serviceDescriptors { - var providerOptions []di.ProvideOption + var providerOptions []ProvideOption if desc.Implements != nil { - providerOptions = append(providerOptions, di.As(desc.Implements)) + providerOptions = append(providerOptions, As(desc.Implements)) } if desc.Name != "" { - providerOptions = append(providerOptions, di.WithName(desc.Name)) + providerOptions = append(providerOptions, WithName(desc.Name)) } //prototype is create a new instance on each call. if desc.Lifetime != Singleton { //providerOptions = append(providerOptions) } - provider := di.Provide(desc.Provider, providerOptions...) + provider := Provide(desc.Provider, providerOptions...) providers = append(providers, provider) } - container, _ := di.New(providers...) + container := New(providers...) return &DefaultServiceProvider{container} } diff --git a/di/container.go b/di/container.go new file mode 100644 index 0000000..70ad433 --- /dev/null +++ b/di/container.go @@ -0,0 +1,179 @@ +package di + +import ( + "fmt" + "github.com/yoyofxteam/dependencyinjection/di/internal/graphkv" + "github.com/yoyofxteam/dependencyinjection/di/internal/reflection" + "reflect" +) + +// Interactor is a helper interface. +type Interactor interface { + Extract(target interface{}, options ...ExtractOption) error + Invoke(fn interface{}, options ...InvokeOption) error +} + +// Builder is helper interface. +type Builder interface { + Provide(provider interface{}, options ...ProvideOption) +} + +// New create new container. +func New() *Container { + return &Container{ + graph: graphkv.New(), + } +} + +// Container is a dependency injection container. +type Container struct { + compiled bool + graph *graphkv.Graph + cleanups []func() +} + +// Provide adds constructor into container with parameters. +func (c *Container) Provide(constructor interface{}, options ...ProvideOption) { + params := ProvideParams{} + for _, opt := range options { + opt.apply(¶ms) + } + provider := internalProvider(newProviderConstructor(params.Name, constructor)) + key := provider.Key() + if c.graph.Exists(key) { + panicf("The `%s` type already exists in container", provider.Key()) + } + if !params.IsPrototype { + provider = asSingleton(provider) + } + // add provider to graph + c.graph.Add(key, provider) + // parse embed parameters + for _, param := range provider.ParameterList() { + if param.embed { + embed := newProviderEmbed(param) + c.graph.Add(embed.Key(), embed) + } + } + // provide parameter bag + if len(params.Parameters) != 0 { + parameterBugProvider := createParameterBugProvider(provider.Key(), params.Parameters) + c.graph.Add(parameterBugProvider.Key(), parameterBugProvider) + } + // process interfaces + for _, iface := range params.Interfaces { + c.processProviderInterface(provider, iface) + } +} + +// Compile compiles the container. It iterates over all nodes +// in graph and register their parameters. +func (c *Container) Compile() { + graphProvider := func() *Graph { return &Graph{graph: c.graph.DOTGraph()} } + interactorProvider := func() Interactor { return c } + c.Provide(graphProvider) + c.Provide(interactorProvider) + for _, node := range c.graph.Nodes() { + c.registerProviderParameters(node.Value.(internalProvider)) + } + if err := c.graph.CheckCycles(); err != nil { + panic(err.Error()) + } + c.compiled = true +} + +// Extract builds instance of target type and fills target pointer. +func (c *Container) Extract(target interface{}, options ...ExtractOption) error { + params := ExtractParams{} + for _, opt := range options { + opt.apply(¶ms) + } + if !c.compiled { + return fmt.Errorf("container not compiled") + } + if target == nil { + return fmt.Errorf("extract target must be a pointer, got `nil`") + } + if !reflection.IsPtr(target) { + return fmt.Errorf("extract target must be a pointer, got `%s`", reflect.TypeOf(target)) + } + typ := reflect.TypeOf(target) + param := parameter{ + name: params.Name, + res: typ.Elem(), + embed: isEmbedParameter(typ), + } + value, err := param.ResolveValue(c) + if err != nil { + return err + } + targetValue := reflect.ValueOf(target).Elem() + targetValue.Set(value) + return nil +} + +// Invoke calls provided function. +func (c *Container) Invoke(fn interface{}, options ...InvokeOption) error { + params := InvokeParams{} + for _, opt := range options { + opt.apply(¶ms) + } + if !c.compiled { + return fmt.Errorf("container not compiled") + } + invoker, err := newInvoker(fn) + if err != nil { + return err + } + return invoker.Invoke(c) +} + +// Cleanup runs destructors in order that was been created. +func (c *Container) Cleanup() { + for _, cleanup := range c.cleanups { + cleanup() + } +} + +// processProviderInterface represents instances as interfaces and groups. +func (c *Container) processProviderInterface(provider internalProvider, as interface{}) { + // create interface from provider + iface := newProviderInterface(provider, as) + key := iface.Key() + if c.graph.Exists(key) { + stub := newProviderStub(key, "have several implementations") + c.graph.Replace(key, stub) + } else { + // add interface node + c.graph.Add(key, iface) + } + // create group + group := newProviderGroup(key) + groupKey := group.Key() + // check exists + if c.graph.Exists(groupKey) { + // if exists use existing group + node := c.graph.Get(groupKey) + group = node.Value.(*providerGroup) + } else { + // else add new group to graph + c.graph.Add(groupKey, group) + } + // add provider reference into group + providerKey := provider.Key() + group.Add(providerKey) +} + +// registerProviderParameters registers provider parameters in a dependency graph. +func (c *Container) registerProviderParameters(p internalProvider) { + for _, param := range p.ParameterList() { + provider, exists := param.ResolveProvider(c) + if exists { + c.graph.Edge(provider.Key(), p.Key()) + continue + } + if !exists && !param.optional { + panicf("%s: dependency %s not exists in container", p.Key(), param) + } + } +} diff --git a/di/container_test.go b/di/container_test.go new file mode 100644 index 0000000..9487215 --- /dev/null +++ b/di/container_test.go @@ -0,0 +1,719 @@ +package di_test + +import ( + "errors" + "fmt" + "github.com/yoyofxteam/dependencyinjection/di" + "github.com/yoyofxteam/dependencyinjection/di/internal/ditest" + "net" + "net/http" + "reflect" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestContainerCompileErrors(t *testing.T) { + t.Run("dependency cycle cause panic", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvide(ditest.NewCycleFooBar) + c.MustProvide(ditest.NewBar) + c.MustCompileError("the graph cannot be cyclic") + }) + + t.Run("not existing dependency cause compile error", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvide(ditest.NewBar) + c.MustCompileError("*ditest.Bar: dependency *ditest.Foo not exists in container") + }) + + t.Run("not existing non pointer dependency cause compile error", func(t *testing.T) { + c := NewTestContainer(t) + type TestStruct struct { + } + + c.MustProvide(func(s TestStruct) bool { + return true + }) + + require.PanicsWithValue(t, "bool: dependency di_test.TestStruct not exists in container", func() { + c.Compile() + }) + }) +} + +func TestContainerProvideErrors(t *testing.T) { + t.Run("provide string cause panic", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvideError("string", "The constructor must be a function like `func([dep1, dep2, ...]) (, [cleanup, error])`, got `string`") + }) + + t.Run("provide nil cause panic", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvideError(nil, "The constructor must be a function like `func([dep1, dep2, ...]) (, [cleanup, error])`, got `nil`") + }) + + t.Run("provide struct pointer cause panic", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvideError(&ditest.Foo{}, "The constructor must be a function like `func([dep1, dep2, ...]) (, [cleanup, error])`, got `*ditest.Foo`") + }) + + t.Run("provide constructor without result cause panic", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvideError(ditest.ConstructorWithoutResult, "The constructor must be a function like `func([dep1, dep2, ...]) (, [cleanup, error])`, got `github.com/defval/inject/v2/di/internal/ditest.ConstructorWithoutResult`") + }) + + t.Run("provide constructor with many results cause panic", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvideError(ditest.ConstructorWithManyResults, "The constructor must be a function like `func([dep1, dep2, ...]) (, [cleanup, error])`, got `github.com/defval/inject/v2/di/internal/ditest.ConstructorWithManyResults`") + }) + + t.Run("provide constructor with incorrect result error argument", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvideError(ditest.ConstructorWithIncorrectResultError, "The constructor must be a function like `func([dep1, dep2, ...]) (, [cleanup, error])`, got `github.com/defval/inject/v2/di/internal/ditest.ConstructorWithIncorrectResultError`") + }) + + t.Run("provide duplicate", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvide(ditest.NewFoo) + c.MustProvideError(ditest.NewFoo, "The `*ditest.Foo` type already exists in container") + }) + + t.Run("provide as not implemented interface cause error", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvide(ditest.NewFoo) + c.MustProvideError(ditest.NewBar, "*ditest.Bar not implement ditest.Barer", new(ditest.Barer)) + }) + + t.Run("provide as not interface cause error", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvide(ditest.NewFoo) + c.MustProvideError(ditest.NewBar, "*ditest.Foo: not a pointer to interface", new(ditest.Foo)) + }) +} + +func TestContainerExtractErrors(t *testing.T) { + t.Run("container panic on trying extract before compilation", func(t *testing.T) { + c := NewTestContainer(t) + foo := &ditest.Foo{} + c.MustProvide(ditest.CreateFooConstructor(foo)) + var extracted *ditest.Foo + c.MustExtractError(&extracted, "container not compiled") + }) + + t.Run("extract into string cause error", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvide(ditest.NewFoo) + c.MustCompile() + c.MustExtractError("string", "extract target must be a pointer, got `string`") + }) + + t.Run("extract into struct cause error", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvide(ditest.NewFoo) + c.MustCompile() + c.MustExtractError(struct{}{}, "extract target must be a pointer, got `struct {}`") + }) + + t.Run("extract into nil cause error", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvide(ditest.NewFoo) + c.MustCompile() + c.MustExtractError(nil, "extract target must be a pointer, got `nil`") + }) + + t.Run("container does not find type because its named", func(t *testing.T) { + c := NewTestContainer(t) + foo := &ditest.Foo{} + c.MustProvideWithName("foo", ditest.CreateFooConstructor(foo)) + c.MustCompile() + + var extracted *ditest.Foo + c.MustExtractError(&extracted, "*ditest.Foo: not exists in container") + }) + + t.Run("extract returns error because dependency constructing failed", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvide(ditest.CreateFooConstructorWithError(errors.New("internal error"))) + c.MustProvide(ditest.NewBar) + c.MustCompile() + var bar *ditest.Bar + c.MustExtractError(&bar, "*ditest.Foo: internal error") + }) + + t.Run("extract interface with multiple implementations cause error", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvide(ditest.NewFoo) + c.MustProvide(ditest.NewBar, new(ditest.Fooer)) + c.MustProvide(ditest.NewBaz, new(ditest.Fooer)) + c.MustCompile() + + var extracted ditest.Fooer + c.MustExtractError(&extracted, "ditest.Fooer: have several implementations") + }) +} + +func TestContainerInvokeErrors(t *testing.T) { + t.Run("invoke function with incorrect signature cause error", func(t *testing.T) { + c := NewTestContainer(t) + c.MustCompile() + c.MustInvokeError(func() *ditest.Foo { + return nil + }, "the invoke function must be a function like `func([dep1, dep2, ...]) [error]`, got `func() *ditest.Foo`") + }) + + t.Run("invoke function with undefined dependency cause error", func(t *testing.T) { + c := NewTestContainer(t) + c.MustCompile() + c.MustInvokeError(func(foo *ditest.Foo) {}, "could not resolve invoke parameters: *ditest.Foo: not exists in container") + }) + + t.Run("invoke before compile cause error", func(t *testing.T) { + c := NewTestContainer(t) + c.MustInvokeError(func() {}, "container not compiled") + }) +} + +func TestContainerProvide(t *testing.T) { + t.Run("container successfully accept simple constructor", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvide(ditest.NewFoo) + }) + + t.Run("container successfully accept constructor with error", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvide(ditest.CreateFooConstructorWithError(nil)) + }) + + t.Run("container successfully accept constructor with cleanup function", func(t *testing.T) { + c := NewTestContainer(t) + + cleanup := func() {} + c.MustProvide(ditest.CreateFooConstructorWithCleanup(cleanup)) + }) + +} + +func TestContainerExtract(t *testing.T) { + t.Run("container extract correct pointer", func(t *testing.T) { + c := NewTestContainer(t) + foo := &ditest.Foo{} + c.MustProvide(ditest.CreateFooConstructor(foo)) + c.MustCompile() + + var extracted *ditest.Foo + c.MustExtractPtr(foo, &extracted) + }) + + t.Run("container extract same pointer on each extraction", func(t *testing.T) { + c := NewTestContainer(t) + foo := &ditest.Foo{} + c.MustProvide(ditest.CreateFooConstructor(foo)) + c.MustCompile() + + var extracted1 *ditest.Foo + c.MustExtractPtr(foo, &extracted1) + + var extracted2 *ditest.Foo + c.MustExtractPtr(foo, &extracted2) + }) + + t.Run("container extract instance if error is nil", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvide(ditest.CreateFooConstructorWithError(nil)) + c.MustCompile() + + var extracted *ditest.Foo + c.MustExtract(&extracted) + }) + + t.Run("container extract instance if cleanup and error is nil", func(t *testing.T) { + c := NewTestContainer(t) + + c.MustProvide(ditest.CreateFooConstructorWithCleanupAndError(nil, nil)) + c.MustCompile() + + var extracted *ditest.Foo + c.MustExtract(&extracted) + }) + + t.Run("container extract correct named pointer", func(t *testing.T) { + c := NewTestContainer(t) + foo := &ditest.Foo{} + c.MustProvideWithName("foo", ditest.CreateFooConstructor(foo)) + c.MustCompile() + + var extracted *ditest.Foo + c.MustExtractWithName("foo", &extracted) + }) + + t.Run("container extract correct interface implementation", func(t *testing.T) { + c := NewTestContainer(t) + bar := &ditest.Bar{} + c.MustProvide(ditest.NewFoo) + c.MustProvide(ditest.CreateBarConstructor(bar), new(ditest.Fooer)) + c.MustCompile() + + var extracted ditest.Fooer + c.MustExtractPtr(bar, &extracted) + }) + + t.Run("container creates group from interface and extract it", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvide(ditest.NewFoo) + c.MustProvide(ditest.NewBar, new(ditest.Fooer)) + c.MustProvide(ditest.NewBaz, new(ditest.Fooer)) + c.MustCompile() + + var group []ditest.Fooer + c.MustExtract(&group) + require.Len(t, group, 2) + }) + + t.Run("container extract new instance of prototype by each extraction", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvide(ditest.NewFoo) + c.MustProvidePrototype(ditest.NewBar) + c.MustCompile() + + var extracted1 *ditest.Bar + c.MustExtract(&extracted1) + var extracted2 *ditest.Bar + c.MustExtract(&extracted2) + + c.MustNotEqualPointer(extracted1, extracted2) + }) + + t.Run("container resolve interactor", func(t *testing.T) { + c := NewTestContainer(t) + foo := ditest.NewFoo() + c.MustProvide(ditest.CreateFooConstructor(foo)) + c.MustCompile() + var interactor di.Interactor + c.MustExtract(&interactor) + var extractedFoo *ditest.Foo + require.NoError(t, interactor.Extract(&extractedFoo)) + c.MustEqualPointer(foo, extractedFoo) + }) +} + +func TestContainerResolve(t *testing.T) { + t.Run("container resolve correct argument", func(t *testing.T) { + c := NewTestContainer(t) + foo := &ditest.Foo{} + c.MustProvide(ditest.CreateFooConstructor(foo)) + c.MustProvide(ditest.NewBar) + c.MustCompile() + + var bar *ditest.Bar + c.MustExtract(&bar) + c.MustEqualPointer(foo, bar.Foo()) + }) + + t.Run("container resolve correct interface implementation", func(t *testing.T) { + c := NewTestContainer(t) + + foo := ditest.NewFoo() + bar := ditest.NewBar(foo) + + c.MustProvide(ditest.CreateFooConstructor(foo)) + c.MustProvide(ditest.CreateBarConstructor(bar), new(ditest.Fooer)) + c.MustProvide(ditest.NewQux) + c.MustCompile() + + var qux *ditest.Qux + c.MustExtract(&qux) + c.MustEqualPointer(bar, qux.Fooer()) + }) + + t.Run("container resolve correct group", func(t *testing.T) { + c := NewTestContainer(t) + + c.MustProvide(ditest.NewFoo) + c.MustProvide(ditest.NewBar, new(ditest.Fooer)) + c.MustProvide(ditest.NewBaz, new(ditest.Fooer)) + c.MustProvide(ditest.NewFooerGroup) + c.MustCompile() + + var bar *ditest.Bar + c.MustExtract(&bar) + + var baz *ditest.Baz + c.MustExtract(&baz) + + var group *ditest.FooerGroup + c.MustExtract(&group) + require.Len(t, group.Fooers(), 2) + c.MustEqualPointer(bar, group.Fooers()[0]) + c.MustEqualPointer(baz, group.Fooers()[1]) + }) +} + +func TestContainerResolveEmbedParameters(t *testing.T) { + t.Run("container resolve embed parameters", func(t *testing.T) { + c := NewTestContainer(t) + foo := ditest.NewFoo() + bar := ditest.NewBar(foo) + c.MustProvide(ditest.CreateFooConstructor(foo)) + c.MustProvide(ditest.CreateBarConstructor(bar)) + c.MustProvide(ditest.NewBazFromParameters) + c.MustCompile() + + var extracted *ditest.Baz + c.MustExtract(&extracted) + c.MustEqualPointer(foo, extracted.Foo()) + c.MustEqualPointer(bar, extracted.Bar()) + }) + + t.Run("container skip optional parameter", func(t *testing.T) { + c := NewTestContainer(t) + foo := ditest.NewFoo() + c.MustProvide(ditest.CreateFooConstructor(foo)) + c.MustProvide(ditest.NewBazFromParameters) + c.MustCompile() + + var extracted *ditest.Baz + c.MustExtract(&extracted) + c.MustEqualPointer(foo, extracted.Foo()) + require.Nil(t, extracted.Bar()) + }) + + t.Run("container resolve optional not existing group as nil", func(t *testing.T) { + c := NewTestContainer(t) + type Params struct { + di.Parameter + Handlers []http.Handler `di:"optional"` + } + c.MustProvide(func(params Params) bool { + return params.Handlers == nil + }) + c.MustCompile() + var extracted bool + c.MustExtract(&extracted) + require.True(t, extracted) + }) + + t.Run("container skip private fields in parameter", func(t *testing.T) { + c := NewTestContainer(t) + type Param struct { + di.Parameter + private []http.Handler `di:"optional"` + Addrs []net.Addr `di:"optional"` + HaveNotTag string + } + c.MustProvide(func(param Param) bool { + return param.Addrs == nil + }) + c.MustCompile() + var extracted bool + c.MustExtract(&extracted) + require.True(t, extracted) + }) +} + +func TestContainerInvoke(t *testing.T) { + t.Run("container call invoke function", func(t *testing.T) { + c := NewTestContainer(t) + c.MustCompile() + var invokeCalled bool + c.MustInvoke(func() { + invokeCalled = true + }) + require.True(t, invokeCalled) + }) + + t.Run("container resolve dependencies in invoke function", func(t *testing.T) { + c := NewTestContainer(t) + foo := ditest.NewFoo() + c.MustProvide(ditest.CreateFooConstructor(foo)) + c.MustCompile() + c.MustInvoke(func(invokeFoo *ditest.Foo) { + c.MustEqualPointer(foo, invokeFoo) + }) + }) + + t.Run("container invoke return correct error", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvide(ditest.NewFoo) + c.Compile() + c.MustInvokeError(func(foo *ditest.Foo) error { + return errors.New("invoke error") + }, "invoke error") + }) + + t.Run("container invoke with nil error", func(t *testing.T) { + c := NewTestContainer(t) + c.MustProvide(ditest.NewFoo) + c.Compile() + c.MustInvoke(func(foo *ditest.Foo) error { + return nil + }) + }) +} + +func TestContainerResolveParameterBag(t *testing.T) { + t.Run("container extract correct parameter bag for type", func(t *testing.T) { + c := NewTestContainer(t) + + c.Provide(ditest.NewFooWithParameters, di.ProvideParams{ + Parameters: di.ParameterBag{ + "name": "test", + }, + }) + + c.MustCompile() + + var foo *ditest.Foo + err := c.Extract(&foo) + + require.NoError(t, err) + require.Equal(t, "test", foo.Name) + }) + + t.Run("container extract correct parameter bag for named type", func(t *testing.T) { + c := NewTestContainer(t) + + c.Provide(ditest.NewFooWithParameters, di.ProvideParams{ + Name: "named", + Parameters: di.ParameterBag{ + "name": "test", + }, + }) + + c.MustCompile() + + var foo *ditest.Foo + err := c.Extract(&foo, di.ExtractParams{ + Name: "named", + }) + + require.NoError(t, err) + require.Equal(t, "test", foo.Name) + }) +} + +func TestContainerCleanup(t *testing.T) { + t.Run("container run cleanup function after container close", func(t *testing.T) { + c := NewTestContainer(t) + var cleanupCalled bool + c.MustProvide(ditest.CreateFooConstructorWithCleanup(func() { cleanupCalled = true })) + c.MustCompile() + + var extracted *ditest.Foo + c.MustExtract(&extracted) + c.Cleanup() + + require.True(t, cleanupCalled) + }) + + t.Run("cleanup run in correct order", func(t *testing.T) { + c := NewTestContainer(t) + var cleanupCalls []string + c.MustProvide(func(bar *ditest.Bar) (*ditest.Foo, func()) { + return &ditest.Foo{}, func() { cleanupCalls = append(cleanupCalls, "foo") } + }) + c.MustProvide(func() (*ditest.Bar, func()) { + return &ditest.Bar{}, func() { cleanupCalls = append(cleanupCalls, "bar") } + }) + c.MustCompile() + + var foo *ditest.Foo + c.MustExtract(&foo) + c.Cleanup() + require.Equal(t, []string{"bar", "foo"}, cleanupCalls) + }) + + t.Run("cleanup for every prototyped instance", func(t *testing.T) { + c := NewTestContainer(t) + var cleanupCalls []string + c.Provide(func() (*ditest.Foo, func()) { + return &ditest.Foo{}, func() { + cleanupCalls = append(cleanupCalls, fmt.Sprintf("foo_%d", len(cleanupCalls))) + } + }, di.ProvideParams{ + IsPrototype: true, + }) + c.MustCompile() + var foo1, foo2 *ditest.Foo + c.MustExtract(&foo1) + c.MustExtract(&foo2) + c.Cleanup() + require.Equal(t, []string{"foo_0", "foo_1"}, cleanupCalls) + }) +} + +func TestContainer_GraphVisualizing(t *testing.T) { + t.Run("graph", func(t *testing.T) { + c := NewTestContainer(t) + + c.MustProvide(ditest.NewLogger) + c.MustProvide(ditest.NewServer) + c.MustProvide(ditest.NewRouter, new(http.Handler)) + c.MustProvide(ditest.NewAccountController, new(ditest.Controller)) + c.MustProvide(ditest.NewAuthController, new(ditest.Controller)) + c.MustCompile() + + var graph *di.Graph + require.NoError(t, c.Extract(&graph)) + + fmt.Println(graph.String()) + + require.Equal(t, `digraph { + subgraph cluster_s3 { + ID = "cluster_s3"; + bgcolor="#E8E8E8";color="lightgrey";fontcolor="#46494C";fontname="COURIER";label="";style="rounded"; + n9[color="#46494C",fontcolor="white",fontname="COURIER",label="*di.Graph",shape="box",style="filled"]; + n10[color="#46494C",fontcolor="white",fontname="COURIER",label="di.Interactor",shape="box",style="filled"]; + + }subgraph cluster_s2 { + ID = "cluster_s2"; + bgcolor="#E8E8E8";color="lightgrey";fontcolor="#46494C";fontname="COURIER";label="";style="rounded"; + n6[color="#46494C",fontcolor="white",fontname="COURIER",label="*ditest.AccountController",shape="box",style="filled"]; + n8[color="#46494C",fontcolor="white",fontname="COURIER",label="*ditest.AuthController",shape="box",style="filled"]; + n7[color="#E54B4B",fontcolor="white",fontname="COURIER",label="[]ditest.Controller",shape="doubleoctagon",style="filled"]; + n4[color="#E5984B",fontcolor="white",fontname="COURIER",label="ditest.RouterParams",shape="box",style="filled"]; + + }subgraph cluster_s0 { + ID = "cluster_s0"; + bgcolor="#E8E8E8";color="lightgrey";fontcolor="#46494C";fontname="COURIER";label="";style="rounded"; + n1[color="#46494C",fontcolor="white",fontname="COURIER",label="*log.Logger",shape="box",style="filled"]; + + }subgraph cluster_s1 { + ID = "cluster_s1"; + bgcolor="#E8E8E8";color="lightgrey";fontcolor="#46494C";fontname="COURIER";label="";style="rounded"; + n3[color="#46494C",fontcolor="white",fontname="COURIER",label="*http.ServeMux",shape="box",style="filled"]; + n2[color="#46494C",fontcolor="white",fontname="COURIER",label="*http.Server",shape="box",style="filled"]; + n5[color="#2589BD",fontcolor="white",fontname="COURIER",label="http.Handler",style="filled"]; + + }splines="ortho"; + n6->n7[color="#949494"]; + n8->n7[color="#949494"]; + n3->n5[color="#949494"]; + n1->n2[color="#949494"]; + n1->n3[color="#949494"]; + n1->n6[color="#949494"]; + n1->n8[color="#949494"]; + n7->n4[color="#949494"]; + n4->n3[color="#949494"]; + n5->n2[color="#949494"]; + +}`, graph.String()) + }) +} + +// NewTestContainer +func NewTestContainer(t *testing.T) *TestContainer { + return &TestContainer{t, di.New()} +} + +// TestContainer +type TestContainer struct { + t *testing.T + *di.Container +} + +func (c *TestContainer) MustProvide(provider interface{}, as ...interface{}) { + require.NotPanics(c.t, func() { + c.Provide(provider, di.ProvideParams{ + Interfaces: as, + }) + }, "provide should not panic") +} + +func (c *TestContainer) MustProvidePrototype(provider interface{}, as ...interface{}) { + require.NotPanics(c.t, func() { + c.Provide(provider, di.ProvideParams{ + Interfaces: as, + IsPrototype: true, + }) + }) +} + +func (c *TestContainer) MustProvideWithName(name string, provider interface{}, as ...interface{}) { + require.NotPanics(c.t, func() { + c.Provide(provider, di.ProvideParams{ + Name: name, + Interfaces: as, + }) + }) +} + +func (c *TestContainer) MustProvideError(provider interface{}, msg string, as ...interface{}) { + require.PanicsWithValue(c.t, msg, func() { + c.Provide(provider, di.ProvideParams{ + Interfaces: as, + }) + }) +} + +func (c *TestContainer) MustCompile() { + require.NotPanics(c.t, func() { + c.Compile() + }) +} + +func (c *TestContainer) MustCompileError(msg string) { + require.PanicsWithValue(c.t, msg, func() { + c.Compile() + }) +} + +func (c *TestContainer) MustExtract(target interface{}) { + require.NoError(c.t, c.Extract(target)) +} + +func (c *TestContainer) MustExtractWithName(name string, target interface{}) { + require.NoError(c.t, c.Extract(target, di.ExtractParams{ + Name: name, + })) +} + +func (c *TestContainer) MustExtractError(target interface{}, msg string) { + require.EqualError(c.t, c.Extract(target, di.ExtractParams{}), msg) +} + +func (c *TestContainer) MustExtractWithNameError(name string, target interface{}, msg string) { + require.EqualError(c.t, c.Extract(target, di.ExtractParams{ + Name: name, + }), msg) +} + +// MustExtractPtr extract value from container into target and check that target and expected pointers are equal. +func (c *TestContainer) MustExtractPtr(expected, target interface{}) { + c.MustExtract(target) + + // indirect + actual := reflect.ValueOf(target).Elem().Interface() + c.MustEqualPointer(expected, actual) +} + +func (c *TestContainer) MustExtractPtrWithName(expected interface{}, name string, target interface{}) { + c.MustExtractWithName(name, target) + + actual := reflect.ValueOf(target).Elem().Interface() + c.MustEqualPointer(expected, actual) +} + +func (c *TestContainer) MustInvoke(fn interface{}) { + require.NoError(c.t, c.Invoke(fn)) +} + +func (c *TestContainer) MustInvokeError(fn interface{}, msg string) { + require.EqualError(c.t, c.Invoke(fn), msg) +} + +func (c *TestContainer) MustEqualPointer(expected interface{}, actual interface{}) { + require.Equal(c.t, + fmt.Sprintf("%p", actual), + fmt.Sprintf("%p", expected), + "actual and expected pointers should be equal", + ) +} + +func (c *TestContainer) MustNotEqualPointer(expected interface{}, actual interface{}) { + require.NotEqual(c.t, + fmt.Sprintf("%p", actual), + fmt.Sprintf("%p", expected), + "actual and expected pointers should not be equal", + ) +} diff --git a/di/dot.go b/di/dot.go new file mode 100644 index 0000000..8430bd3 --- /dev/null +++ b/di/dot.go @@ -0,0 +1,20 @@ +package di + +import ( + "io" + + "github.com/emicklei/dot" +) + +// Graph +type Graph struct { + graph *dot.Graph +} + +func (g *Graph) WriteTo(writer io.Writer) { + g.graph.Write(writer) +} + +func (g *Graph) String() string { + return g.graph.String() +} diff --git a/di/errors.go b/di/errors.go new file mode 100644 index 0000000..2a9a39c --- /dev/null +++ b/di/errors.go @@ -0,0 +1,22 @@ +package di + +import "fmt" + +// ErrParameterProvideFailed +type ErrParameterProvideFailed struct { + k key + err error +} + +func (e ErrParameterProvideFailed) Error() string { + return fmt.Sprintf("%s: %s", e.k, e.err) +} + +// ErrParameterProviderNotFound +type ErrParameterProviderNotFound struct { + param parameter +} + +func (e ErrParameterProviderNotFound) Error() string { + return fmt.Sprintf("%s: not exists in container", e.param) +} diff --git a/di/internal/ditest/bar.go b/di/internal/ditest/bar.go new file mode 100644 index 0000000..5204308 --- /dev/null +++ b/di/internal/ditest/bar.go @@ -0,0 +1,23 @@ +package ditest + +// Bar +type Bar struct { + foo *Foo +} + +// NewBar +func NewBar(foo *Foo) *Bar { + return &Bar{ + foo: foo, + } +} + +// CreateBarConstructor +func CreateBarConstructor(bar *Bar) func(foo *Foo) *Bar { + return func(foo *Foo) *Bar { + bar.foo = foo + return bar + } +} + +func (b *Bar) Foo() *Foo { return b.foo } diff --git a/di/internal/ditest/baz.go b/di/internal/ditest/baz.go new file mode 100644 index 0000000..76ef1af --- /dev/null +++ b/di/internal/ditest/baz.go @@ -0,0 +1,36 @@ +package ditest + +import "github.com/yoyofxteam/dependencyinjection/di" + +// Baz +type Baz struct { + foo *Foo + bar *Bar +} + +// NewBaz +func NewBaz(foo *Foo, bar *Bar) *Baz { + return &Baz{ + foo: foo, + bar: bar, + } +} + +// BazParameters +type BazParameters struct { + di.Parameter + + Foo *Foo `di:""` + Bar *Bar `di:"optional"` +} + +// NewBazFromParameters +func NewBazFromParameters(params BazParameters) *Baz { + return &Baz{ + foo: params.Foo, + bar: params.Bar, + } +} + +func (b *Baz) Foo() *Foo { return b.foo } +func (b *Baz) Bar() *Bar { return b.bar } diff --git a/di/internal/ditest/foo.go b/di/internal/ditest/foo.go new file mode 100644 index 0000000..657d262 --- /dev/null +++ b/di/internal/ditest/foo.go @@ -0,0 +1,51 @@ +package ditest + +import "github.com/yoyofxteam/dependencyinjection/di" + +// Foo test struct +type Foo struct { + Name string +} + +// NewFoo create new foo +func NewFoo() *Foo { + return &Foo{} +} + +// NewFooWithParameters +func NewFooWithParameters(parameters di.ParameterBag) *Foo { + return &Foo{Name: parameters.RequireString("name")} +} + +// NewCycleFooBar +func NewCycleFooBar(bar *Bar) *Foo { + return &Foo{} +} + +// CreateFooConstructor +func CreateFooConstructor(foo *Foo) func() *Foo { + return func() *Foo { + return foo + } +} + +// CreateFooConstructorWithError +func CreateFooConstructorWithError(err error) func() (*Foo, error) { + return func() (foo *Foo, e error) { + return &Foo{}, err + } +} + +// CreateFooConstructorWithCleanup +func CreateFooConstructorWithCleanup(cleanup func()) func() (*Foo, func()) { + return func() (foo *Foo, i func()) { + return &Foo{}, cleanup + } +} + +// CreateFooConstructorWithCleanupAndError +func CreateFooConstructorWithCleanupAndError(cleanup func(), err error) func() (*Foo, func(), error) { + return func() (foo *Foo, i func(), e error) { + return &Foo{}, cleanup, err + } +} diff --git a/di/internal/ditest/fooer_group.go b/di/internal/ditest/fooer_group.go new file mode 100644 index 0000000..c668624 --- /dev/null +++ b/di/internal/ditest/fooer_group.go @@ -0,0 +1,15 @@ +package ditest + +// FooerGroup +type FooerGroup struct { + fooers []Fooer +} + +// NewFooerGroup +func NewFooerGroup(fooers []Fooer) *FooerGroup { + return &FooerGroup{fooers: fooers} +} + +func (g *FooerGroup) Fooers() []Fooer { + return g.fooers +} diff --git a/di/internal/ditest/full.go b/di/internal/ditest/full.go new file mode 100644 index 0000000..e5ab8bf --- /dev/null +++ b/di/internal/ditest/full.go @@ -0,0 +1,83 @@ +package ditest + +import ( + "github.com/yoyofxteam/dependencyinjection/di" + "log" + "net/http" + "os" +) + +// NewLogger +func NewLogger() *log.Logger { + logger := log.New(os.Stdout, "", 0) + defer logger.Println("Logger loaded!") + + return logger +} + +// NewServer +func NewServer(logger *log.Logger, handler http.Handler) *http.Server { + defer logger.Println("Server created!") + return &http.Server{ + Handler: handler, + } +} + +// RouterParams +type RouterParams struct { + di.Parameter + Controllers []Controller `di:"optional"` +} + +// NewRouter +func NewRouter(logger *log.Logger, params RouterParams) *http.ServeMux { + logger.Println("Create router!") + defer logger.Println("Router created!") + + mux := &http.ServeMux{} + + for _, ctrl := range params.Controllers { + ctrl.RegisterRoutes(mux) + } + + return mux +} + +// Controller +type Controller interface { + RegisterRoutes(mux *http.ServeMux) +} + +// AccountController +type AccountController struct { + Logger *log.Logger +} + +// NewAccountController +func NewAccountController(logger *log.Logger) *AccountController { + return &AccountController{Logger: logger} +} + +// RegisterRoutes +func (c *AccountController) RegisterRoutes(mux *http.ServeMux) { + c.Logger.Println("AccountController registered!") + + // register your routes +} + +// AuthController +type AuthController struct { + Logger *log.Logger +} + +// NewAuthController +func NewAuthController(logger *log.Logger) *AuthController { + return &AuthController{Logger: logger} +} + +// RegisterRoutes +func (c *AuthController) RegisterRoutes(mux *http.ServeMux) { + c.Logger.Println("AuthController registered!") + + // register your routes +} diff --git a/di/internal/ditest/incorrect.go b/di/internal/ditest/incorrect.go new file mode 100644 index 0000000..20505b5 --- /dev/null +++ b/di/internal/ditest/incorrect.go @@ -0,0 +1,16 @@ +package ditest + +// ConstructorWithoutResult +func ConstructorWithoutResult() { + +} + +// ConstructorWithManyResults +func ConstructorWithManyResults() (*Foo, *Bar, error) { + return &Foo{}, &Bar{}, nil +} + +// ConstructorWithIncorrectResultError +func ConstructorWithIncorrectResultError() (*Foo, *Bar) { + return &Foo{}, &Bar{} +} diff --git a/di/internal/ditest/interfaces.go b/di/internal/ditest/interfaces.go new file mode 100644 index 0000000..b40e2fa --- /dev/null +++ b/di/internal/ditest/interfaces.go @@ -0,0 +1,16 @@ +package ditest + +// Fooer +type Fooer interface { + Foo() *Foo +} + +// Barer +type Barer interface { + Bar() *Bar +} + +// Bazer +type Bazer interface { + Baz() *Baz +} diff --git a/di/internal/ditest/qux.go b/di/internal/ditest/qux.go new file mode 100644 index 0000000..abb2e48 --- /dev/null +++ b/di/internal/ditest/qux.go @@ -0,0 +1,15 @@ +package ditest + +// Qux +type Qux struct { + fooer Fooer +} + +// NewQux +func NewQux(foo Fooer) *Qux { + return &Qux{ + fooer: foo, + } +} + +func (q *Qux) Fooer() Fooer { return q.fooer } diff --git a/di/internal/graphkv/directed_graph.go b/di/internal/graphkv/directed_graph.go new file mode 100644 index 0000000..12f167c --- /dev/null +++ b/di/internal/graphkv/directed_graph.go @@ -0,0 +1,149 @@ +package graphkv + +// directedGraph is a graph supporting directed edges between nodes. +type directedGraph struct { + *graph + edges *directedEdgeList +} + +// newDirectedGraph creates a graph of nodes with directed edges. +func newDirectedGraph() *directedGraph { + return &directedGraph{ + graph: newGraph(), + edges: newDirectedEdgeList(), + } +} + +// Copy returns a clone of the directed graph. +func (g *directedGraph) Copy() *directedGraph { + return &directedGraph{ + graph: g.graph.Copy(), + edges: g.edges.Copy(), + } +} + +// EdgeCount returns the number of direced edges between nodes. +func (g *directedGraph) EdgeCount() int { + return g.edges.Count() +} + +// AddEdge adds the edge to the graph. +func (g *directedGraph) AddEdge(from Key, to Key) { + // prevent adding an edge referring to missing nodes + if !g.NodeExists(from) { + g.AddNode(from) + } + if !g.NodeExists(to) { + g.AddNode(to) + } + + g.edges.Add(from, to) +} + +// RemoveEdge removes the edge from the graph. +func (g *directedGraph) RemoveEdge(from Key, to Key) { + g.edges.Remove(from, to) +} + +// HasEdges determines whether the graph contains any edges to or from the node. +func (g *directedGraph) HasEdges(node Key) bool { + if g.HasIncomingEdges(node) { + return true + } + return g.HasOutgoingEdges(node) +} + +// EdgeExists checks whether the edge exists within the graph. +func (g *directedGraph) EdgeExists(from Key, to Key) bool { + return g.edges.Exists(from, to) +} + +// HasIncomingEdges checks whether the graph contains any directed +// edges pointing to the node. +func (g *directedGraph) HasIncomingEdges(node Key) bool { + return g.edges.HasIncomingEdges(node) +} + +// IncomingEdges returns the nodes belonging to directed edges pointing +// towards the specified node. +func (g *directedGraph) IncomingEdges(node Key) []Key { + return g.edges.IncomingEdges(node) +} + +// IncomingEdgeCount returns the number of edges pointing from the specified +// node (indegree). +func (g *directedGraph) IncomingEdgeCount(node Key) int { + return g.edges.IncomingEdgeCount(node) +} + +// HasOutgoingEdges checks whether the graph contains any directed +// edges pointing from the node. +func (g *directedGraph) HasOutgoingEdges(node Key) bool { + return g.edges.HasOutgoingEdges(node) +} + +// OutgoingEdges returns the nodes belonging to directed edges pointing +// from the specified node. +func (g *directedGraph) OutgoingEdges(node Key) []Key { + return g.edges.OutgoingEdges(node) +} + +// OutgoingEdgeCount returns the number of edges pointing from the specified +// node (outdegree). +func (g *directedGraph) OutgoingEdgeCount(node Key) int { + return g.edges.OutgoingEdgeCount(node) +} + +// RootNodes finds the entry-point nodes to the graph, i.e. those without +// incoming edges. +func (g *directedGraph) RootNodes() []Key { + results := make([]Key, 0) + for _, node := range g.Nodes() { + if !g.HasIncomingEdges(node) { + results = append(results, node) + } + } + return results +} + +// IsolatedNodes finds independent nodes in the graph, i.e. those without edges. +func (g *directedGraph) IsolatedNodes() []Key { + results := make([]Key, 0) + for _, node := range g.Nodes() { + if !g.HasEdges(node) { + results = append(results, node) + } + } + return results +} + +// AdjacencyMatrix returns a matrix indicating whether pairs of nodes are +// adjacent or not within the graph. +func (g *directedGraph) AdjacencyMatrix() map[Key]map[Key]bool { + matrix := make(map[Key]map[Key]bool, g.NodeCount()) + for _, a := range g.Nodes() { + matrix[a] = make(map[Key]bool, g.NodeCount()) + + for _, b := range g.Nodes() { + matrix[a][b] = g.EdgeExists(a, b) + } + } + return matrix +} + +// RemoveTransitives removes any transitive edges so that as fewest possible +// edges exist while matching the reachability of the original graph. +func (g *directedGraph) RemoveTransitives() { + for _, a := range g.Nodes() { + for _, b := range g.Nodes() { + if !g.EdgeExists(a, b) { + continue + } + for _, c := range g.Nodes() { + if g.EdgeExists(b, c) { + g.RemoveEdge(a, c) + } + } + } + } +} diff --git a/di/internal/graphkv/directed_graph_test.go b/di/internal/graphkv/directed_graph_test.go new file mode 100644 index 0000000..52af780 --- /dev/null +++ b/di/internal/graphkv/directed_graph_test.go @@ -0,0 +1,146 @@ +package graphkv + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func newTestDirectedGraph() *directedGraph { + graph := newDirectedGraph() + graph.AddNodes("A", "B", "C", "D") + return graph +} + +func TestNewDirectedGraph(t *testing.T) { + graph := newDirectedGraph() + assert.NotNil(t, graph, "graph should not be nil") + assert.Zero(t, graph.NodeCount(), "graph.NodeCount() should equal zero") + assert.Empty(t, graph.Nodes(), "graph.Nodes() should equal empty") + assert.Zero(t, graph.EdgeCount(), "graph.EdgeCount() should equal zero") +} + +func TestDirectedGraphAddEdge(t *testing.T) { + graph := newTestDirectedGraph() + graph.AddEdge("A", "B") + graph.AddEdge("B", "D") + graph.AddEdge("C", "B") + + assert.Equal(t, 3, graph.EdgeCount(), "graph.EdgeCount() should equal 3") + assert.True(t, graph.EdgeExists("A", "B"), "graph.EdgeExists(A, B) should equal true") + assert.True(t, graph.EdgeExists("B", "D"), "graph.EdgeExists(B, D) should equal true") + assert.True(t, graph.EdgeExists("C", "B"), "graph.EdgeExists(C, B) should equal true") +} + +func TestDirectedGraphAddEdgeDuplicate(t *testing.T) { + graph := newTestDirectedGraph() + graph.AddEdge("A", "B") + graph.AddEdge("B", "C") + graph.AddEdge("B", "C") + + assert.Equal(t, 2, graph.EdgeCount(), "graph.EdgeCount() should equal 2") + assert.True(t, graph.EdgeExists("A", "B"), "graph.EdgeExists(A, B) should equal true") + assert.True(t, graph.EdgeExists("B", "C"), "graph.EdgeExists(B, C) should equal true") +} + +func TestDirectedGraphAddEdgeMissingNodes(t *testing.T) { + graph := newDirectedGraph() + graph.AddEdge("A", "B") + graph.AddEdge("B", "C") + + assert.Equal(t, 3, graph.NodeCount(), "graph.NodeCount() should equal 2") + assert.Equal(t, 2, graph.EdgeCount(), "graph.EdgeCount() should equal 2") + assert.True(t, graph.EdgeExists("A", "B"), "graph.EdgeExists(A, B) should equal true") + assert.True(t, graph.EdgeExists("B", "C"), "graph.EdgeExists(B, C) should equal true") +} + +func TestDirectedGraphRemoveEdge(t *testing.T) { + graph := newTestDirectedGraph() + graph.AddEdge("A", "B") + graph.AddEdge("B", "D") + graph.AddEdge("C", "B") + graph.RemoveEdge("A", "B") + graph.RemoveEdge("C", "B") + + assert.Equal(t, 1, graph.EdgeCount(), "graph.EdgeCount() should equal 1") + assert.False(t, graph.EdgeExists("A", "B"), "graph.EdgeExists(A, B) should equal false") + assert.False(t, graph.EdgeExists("C", "B"), "graph.EdgeExists(C, B) should equal false") + assert.True(t, graph.EdgeExists("B", "D"), "graph.EdgeExists(B, D) should equal true") +} + +func TestDirectedGraphRemoveEdgeMissing(t *testing.T) { + graph := newTestDirectedGraph() + graph.AddEdge("C", "B") + graph.RemoveEdge("D", "A") + graph.RemoveEdge("C", "B") + + assert.Zero(t, graph.EdgeCount(), "graph.EdgeCount() should equal zero") +} + +func TestDirectedGraphHasEdges(t *testing.T) { + graph := newTestDirectedGraph() + graph.AddEdge("A", "C") + + assert.True(t, graph.HasEdges("A"), "graph.HasEdges(A) should equal true") + assert.False(t, graph.HasEdges("B"), "graph.HasEdges(B) should equal false") + assert.True(t, graph.HasEdges("C"), "graph.HasEdges(C) should equal true") + assert.False(t, graph.HasEdges("D"), "graph.HasEdges(D) should equal false") +} + +func TestDirectedGraphIncomingEdgeCount(t *testing.T) { + graph := newTestDirectedGraph() + graph.AddEdge("A", "C") + graph.AddEdge("B", "C") + + assert.Zero(t, graph.IncomingEdgeCount("A"), "graph.IncomingEdgeCount(A) should equal 0") + assert.Zero(t, graph.IncomingEdgeCount("B"), "graph.IncomingEdgeCount(B) should equal 0") + assert.Equal(t, 2, graph.IncomingEdgeCount("C"), "graph.IncomingEdgeCount(C) should equal 1") + assert.Zero(t, graph.IncomingEdgeCount("D"), "graph.IncomingEdgeCount(D) should equal 0") +} + +func TestDirectedGraphOutgoingEdgeCount(t *testing.T) { + graph := newTestDirectedGraph() + graph.AddEdge("A", "B") + graph.AddEdge("A", "C") + + assert.Equal(t, 2, graph.OutgoingEdgeCount("A"), "graph.OutgoingEdgeCount(A) should equal 2") + assert.Zero(t, graph.OutgoingEdgeCount("B"), "graph.OutgoingEdgeCount(B) should equal 0") + assert.Zero(t, graph.OutgoingEdgeCount("C"), "graph.OutgoingEdgeCount(C) should equal 0") + assert.Zero(t, graph.OutgoingEdgeCount("D"), "graph.OutgoingEdgeCount(D) should equal 0") +} + +func TestDirectedGraphRootNodes(t *testing.T) { + graph := newTestDirectedGraph() + graph.AddEdge("A", "B") + graph.AddEdge("B", "C") + graph.AddEdge("D", "C") + graph.AddEdge("E", "C") + graph.AddEdge("F", "E") + + assert.Equal(t, []Key{"A", "D", "F"}, graph.RootNodes(), "graph.RootNodes() should equal [A, D, F]") +} + +func TestDirectedGraphIsolatedNodes(t *testing.T) { + graph := newTestDirectedGraph() + graph.AddEdge("A", "C") + + assert.Equal(t, []Key{"B", "D"}, graph.IsolatedNodes(), "graph.IsolatedNodes() should equal [B, D]") +} + +func TestDirectedGraphAdjacencyMatrix(t *testing.T) { + graph := newTestDirectedGraph() + graph.AddEdge("A", "C") + graph.AddEdge("A", "B") + graph.AddEdge("B", "D") + graph.AddEdge("C", "A") + graph.AddEdge("D", "D") + + expected := map[interface{}]map[interface{}]bool{ + "A": map[interface{}]bool{"A": false, "B": true, "C": true, "D": false}, + "B": map[interface{}]bool{"A": false, "B": false, "C": false, "D": true}, + "C": map[interface{}]bool{"A": true, "B": false, "C": false, "D": false}, + "D": map[interface{}]bool{"D": true, "A": false, "B": false, "C": false}, + } + + assert.Equal(t, expected, graph.AdjacencyMatrix(), "graph.AdjacencyMatrix() should equal [B, D]") +} diff --git a/di/internal/graphkv/edge.go b/di/internal/graphkv/edge.go new file mode 100644 index 0000000..df1a2f5 --- /dev/null +++ b/di/internal/graphkv/edge.go @@ -0,0 +1,125 @@ +package graphkv + +type directedEdgeList struct { + outgoingEdges map[Key]*nodeList + incomingEdges map[Key]*nodeList +} + +func newDirectedEdgeList() *directedEdgeList { + return &directedEdgeList{ + outgoingEdges: make(map[Key]*nodeList), + incomingEdges: make(map[Key]*nodeList), + } +} + +func (l *directedEdgeList) Copy() *directedEdgeList { + outgoingEdges := make(map[Key]*nodeList, len(l.outgoingEdges)) + for node, edges := range l.outgoingEdges { + outgoingEdges[node] = edges.Copy() + } + + incomingEdges := make(map[Key]*nodeList, len(l.incomingEdges)) + for node, edges := range l.incomingEdges { + incomingEdges[node] = edges.Copy() + } + + return &directedEdgeList{ + outgoingEdges: outgoingEdges, + incomingEdges: incomingEdges, + } +} + +func (l *directedEdgeList) Count() int { + return len(l.outgoingEdges) +} + +func (l *directedEdgeList) HasOutgoingEdges(node Key) bool { + _, ok := l.outgoingEdges[node] + return ok +} + +func (l *directedEdgeList) OutgoingEdgeCount(node Key) int { + if list := l.outgoingNodeList(node, false); list != nil { + return list.Count() + } + return 0 +} + +func (l *directedEdgeList) outgoingNodeList(node Key, create bool) *nodeList { + if list, ok := l.outgoingEdges[node]; ok { + return list + } + if create { + list := newNodeList() + l.outgoingEdges[node] = list + return list + } + return nil +} + +func (l *directedEdgeList) OutgoingEdges(node Key) []Key { + if list := l.outgoingNodeList(node, false); list != nil { + return list.Nodes() + } + return nil +} + +func (l *directedEdgeList) HasIncomingEdges(node Key) bool { + _, ok := l.incomingEdges[node] + return ok +} + +func (l *directedEdgeList) IncomingEdgeCount(node Key) int { + if list := l.incomingNodeList(node, false); list != nil { + return list.Count() + } + return 0 +} + +func (l *directedEdgeList) incomingNodeList(node Key, create bool) *nodeList { + if list, ok := l.incomingEdges[node]; ok { + return list + } + if create { + list := newNodeList() + l.incomingEdges[node] = list + return list + } + return nil +} + +func (l *directedEdgeList) IncomingEdges(node Key) []Key { + if list := l.incomingNodeList(node, false); list != nil { + return list.Nodes() + } + return nil +} + +func (l *directedEdgeList) Add(from Key, to Key) { + l.outgoingNodeList(from, true).Add(to) + l.incomingNodeList(to, true).Add(from) +} + +func (l *directedEdgeList) Remove(from Key, to Key) { + if list := l.outgoingNodeList(from, false); list != nil { + list.Remove(to) + + if list.Count() == 0 { + delete(l.outgoingEdges, from) + } + } + if list := l.incomingNodeList(to, false); list != nil { + list.Remove(from) + + if list.Count() == 0 { + delete(l.incomingEdges, to) + } + } +} + +func (l *directedEdgeList) Exists(from Key, to Key) bool { + if list := l.outgoingNodeList(from, false); list != nil { + return list.Exists(to) + } + return false +} diff --git a/di/internal/graphkv/errors.go b/di/internal/graphkv/errors.go new file mode 100644 index 0000000..1110db3 --- /dev/null +++ b/di/internal/graphkv/errors.go @@ -0,0 +1,22 @@ +package graphkv + +import "fmt" + +// ErrKeyAlreadyExists +type ErrKeyAlreadyExists struct { + Key Key +} + +func (e ErrKeyAlreadyExists) Error() string { + return fmt.Sprintf("%s already exists", e.Key) +} + +// ErrNodeNotExists +type ErrNodeNotExists struct { + Key Key +} + +// ErrNodeNotExists +func (e ErrNodeNotExists) Error() string { + return fmt.Sprintf("%s not exists", e.Key) +} diff --git a/di/internal/graphkv/graph.go b/di/internal/graphkv/graph.go new file mode 100644 index 0000000..13de287 --- /dev/null +++ b/di/internal/graphkv/graph.go @@ -0,0 +1,60 @@ +package graphkv + +type graph struct { + nodes *nodeList +} + +func newGraph() *graph { + return &graph{ + nodes: newNodeList(), + } +} + +// Copy returns a clone of the graph. +func (g *graph) Copy() *graph { + return &graph{ + nodes: g.nodes.Copy(), + } +} + +// Nodes returns the graph's nodes. +// The slice is mutable for performance reasons but should not be mutated. +func (g *graph) Nodes() []Key { + return g.nodes.Nodes() +} + +// NodeCount returns the number of nodes. +func (g *graph) NodeCount() int { + return g.nodes.Count() +} + +// AddNode inserts the specified node into the graph. +// A node can be any value, e.g. int, string, pointer to a struct, map etc. +// Duplicate nodes are ignored. +func (g *graph) AddNode(node Key) { + g.AddNodes(node) +} + +// AddNodes inserts the specified nodes into the graph. +// A node can be any value, e.g. int, string, pointer to a struct, map etc. +// Duplicate nodes are ignored. +func (g *graph) AddNodes(nodes ...Key) { + g.nodes.Add(nodes...) +} + +// RemoveNode removes the specified nodes from the graph. +// If the node does not exist within the graph the call will fail silently. +func (g *graph) RemoveNode(node Key) { + g.RemoveNodes(node) +} + +// RemoveNodes removes the specified nodes from the graph. +// If a node does not exist within the graph the call will fail silently. +func (g *graph) RemoveNodes(nodes ...Key) { + g.nodes.Remove(nodes...) +} + +// NodeExists determines whether the specified node exists within the graph. +func (g *graph) NodeExists(node Key) bool { + return g.nodes.Exists(node) +} diff --git a/di/internal/graphkv/graph_test.go b/di/internal/graphkv/graph_test.go new file mode 100644 index 0000000..9065dd6 --- /dev/null +++ b/di/internal/graphkv/graph_test.go @@ -0,0 +1,111 @@ +package graphkv + +import ( + "fmt" + "math" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewGraph(t *testing.T) { + graph := newGraph() + assert.NotNil(t, graph, "graph should not be nil") + assert.Zero(t, graph.NodeCount(), "graph.NodeCount() should equal zero") + assert.Empty(t, graph.Nodes(), "graph.Nodes() should equal empty") +} + +func TestGraphAddNode(t *testing.T) { + graph := newGraph() + graph.AddNode("A") + graph.AddNode("B") + graph.AddNode("C") + + assert.Equal(t, 3, graph.NodeCount(), "graph.NodeCount() should equal 3") + assert.Equal(t, []interface{}{"A", "B", "C"}, graph.Nodes(), "graph.Nodes() should equal [A, B, C]") +} + +func TestGraphAddNodeDuplicate(t *testing.T) { + graph := newGraph() + graph.AddNode("A") + graph.AddNode("B") + graph.AddNode("C") + graph.AddNode("A") + + assert.Equal(t, 3, graph.NodeCount(), "graph.NodeCount() should equal 3") + assert.Equal(t, []interface{}{"A", "B", "C"}, graph.Nodes(), "graph.Nodes() should equal [A, B, C]") +} + +func BenchmarkGraphAddNodes(b *testing.B) { + for i := 12.0; i <= 20; i++ { + count := int(math.Pow(2, i)) + + b.Run(fmt.Sprintf("%d", count), func(b *testing.B) { + graph := newGraph() + for i := 0; i < count; i++ { + graph.AddNode(i) + } + }) + } +} + +func TestGraphAddNodes(t *testing.T) { + graph := newGraph() + graph.AddNodes("A", "B", "C") + + assert.Equal(t, 3, graph.NodeCount(), "graph.NodeCount() should equal 3") + assert.Equal(t, []interface{}{"A", "B", "C"}, graph.Nodes(), "graph.Nodes() should equal [A, B, C]") +} + +func TestGraphRemoveNode(t *testing.T) { + graph := newGraph() + graph.AddNode("A") + graph.AddNode("B") + graph.AddNode("C") + graph.AddNode("D") + graph.RemoveNode("A") + graph.RemoveNode("C") + + assert.Equal(t, 2, graph.NodeCount(), "graph.NodeCount() should equal 2") + assert.Equal(t, []interface{}{"B", "D"}, graph.Nodes(), "graph.Nodes() should equal [B, D]") +} + +func TestGraphRemoveNodeMissing(t *testing.T) { + graph := newGraph() + graph.AddNode("A") + graph.AddNode("B") + graph.AddNode("C") + graph.AddNode("D") + graph.RemoveNode("A") + graph.RemoveNode("A") + graph.RemoveNode("E") + + assert.Equal(t, 3, graph.NodeCount(), "graph.NodeCount() should equal 2") + assert.Equal(t, []interface{}{"B", "C", "D"}, graph.Nodes(), "graph.Nodes() should equal [B, C, D]") +} + +func TestGraphRemoveNodes(t *testing.T) { + graph := newGraph() + graph.AddNode("A") + graph.AddNode("B") + graph.AddNode("C") + graph.AddNode("D") + graph.RemoveNodes("A", "C") + + assert.Equal(t, 2, graph.NodeCount(), "graph.NodeCount() should equal 2") + assert.Equal(t, []interface{}{"B", "D"}, graph.Nodes(), "graph.Nodes() should equal [B, D]") +} + +func TestGraphNodeExists(t *testing.T) { + graph := newGraph() + assert.False(t, graph.NodeExists("A"), "graph.NodeExists(\"A\") should equal false") + assert.False(t, graph.NodeExists("B"), "graph.NodeExists(\"B\") should equal false") + + graph.AddNode("A") + assert.True(t, graph.NodeExists("A"), "graph.NodeExists(\"A\") should equal true") + assert.False(t, graph.NodeExists("B"), "graph.NodeExists(\"B\") should equal false") + + graph.RemoveNode("A") + assert.False(t, graph.NodeExists("A"), "graph.NodeExists(\"A\") should equal false") + assert.False(t, graph.NodeExists("B"), "graph.NodeExists(\"B\") should equal false") +} diff --git a/di/internal/graphkv/graphkv.go b/di/internal/graphkv/graphkv.go new file mode 100644 index 0000000..e6691d5 --- /dev/null +++ b/di/internal/graphkv/graphkv.go @@ -0,0 +1,75 @@ +package graphkv + +import ( + "github.com/emicklei/dot" +) + +// Node +type Node struct { + Key Key + Value interface{} +} + +// Graph +type Graph struct { + dag *directedGraph + values map[Key]interface{} +} + +// New +// AddNode +// NodeExists +// Sort +// AddEdge +func New() *Graph { + return &Graph{ + dag: newDirectedGraph(), + values: map[Key]interface{}{}, + } +} + +// Get +func (g *Graph) Get(key Key) Node { + return Node{Key: key, Value: g.values[key]} +} + +// Replace +func (g *Graph) Replace(key Key, value interface{}) { + g.values[key] = value +} + +// Add +func (g *Graph) Add(key Key, value interface{}) { + g.dag.AddNode(key) + g.values[key] = value +} + +// Edge +func (g *Graph) Edge(from Key, to Key) { + g.dag.AddEdge(from, to) +} + +// Exists +func (g *Graph) Exists(key Key) bool { + return g.dag.NodeExists(key) +} + +// Nodes +func (g *Graph) Nodes() []Node { + var nodes []Node + for _, key := range g.dag.Nodes() { + nodes = append(nodes, Node{key, g.values[key]}) + } + return nodes +} + +// CheckCycles +func (g *Graph) CheckCycles() error { + _, err := g.dag.DFSSort() + return err // todo: errors +} + +// DOTGraph +func (g *Graph) DOTGraph() *dot.Graph { + return g.dag.DOTGraph() +} diff --git a/di/internal/graphkv/graphkv_test.go b/di/internal/graphkv/graphkv_test.go new file mode 100644 index 0000000..85693fe --- /dev/null +++ b/di/internal/graphkv/graphkv_test.go @@ -0,0 +1,3 @@ +package graphkv + +// todo: tests diff --git a/di/internal/graphkv/key.go b/di/internal/graphkv/key.go new file mode 100644 index 0000000..fda20e5 --- /dev/null +++ b/di/internal/graphkv/key.go @@ -0,0 +1,75 @@ +package graphkv + +// Key represents a graph node. +type Key = interface{} + +type nodeList struct { + nodes []Key + set map[Key]bool +} + +func newNodeList() *nodeList { + return &nodeList{ + nodes: make([]Key, 0), + set: make(map[Key]bool), + } +} + +func (l *nodeList) Copy() *nodeList { + nodes := make([]Key, len(l.nodes)) + copy(nodes, l.nodes) + + set := make(map[Key]bool, len(nodes)) + for _, node := range nodes { + set[node] = true + } + + return &nodeList{ + nodes: nodes, + set: set, + } +} + +func (l *nodeList) Nodes() []Key { + return l.nodes +} + +func (l *nodeList) Count() int { + return len(l.nodes) +} + +func (l *nodeList) Exists(node Key) bool { + _, ok := l.set[node] + return ok +} + +func (l *nodeList) Add(nodes ...Key) { + for _, node := range nodes { + if l.Exists(node) { + continue + } + + l.nodes = append(l.nodes, node) + l.set[node] = true + } +} + +func (l *nodeList) Remove(nodes ...Key) { + for i := len(l.nodes) - 1; i >= 0; i-- { + for j, node := range nodes { + if l.nodes[i] == node { + copy(l.nodes[i:], l.nodes[i+1:]) + l.nodes[len(l.nodes)-1] = nil + l.nodes = l.nodes[:len(l.nodes)-1] + + delete(l.set, node) + + copy(nodes[j:], nodes[j+1:]) + nodes[len(nodes)-1] = nil + nodes = nodes[:len(nodes)-1] + + break + } + } + } +} diff --git a/di/internal/graphkv/output.go b/di/internal/graphkv/output.go new file mode 100644 index 0000000..4222613 --- /dev/null +++ b/di/internal/graphkv/output.go @@ -0,0 +1,62 @@ +package graphkv + +import ( + "fmt" + + "github.com/emicklei/dot" +) + +// NodeVisualizer +type NodeVisualizer interface { + Visualize(node *dot.Node) + SubGraph() string + IsAlwaysVisible() bool +} + +// DOTGraph returns a textual representation of the graph in the DOT graph +// description language. +func (g *directedGraph) DOTGraph() *dot.Graph { + root := dot.NewGraph(dot.Directed) + root.Attr("splines", "ortho") + + subgraphs := make(map[string]*dot.Graph) + itemsByNode := make(map[Key]dot.Node) + for _, node := range g.Nodes() { + nv := node.(NodeVisualizer) + + if !g.HasOutgoingEdges(node) && !nv.IsAlwaysVisible() { + continue + } + + name := fmt.Sprintf("%s", node) + subgraph, ok := subgraphs[nv.SubGraph()] + if !ok { + subgraph = root.Subgraph(nv.SubGraph(), dot.ClusterOption{}) + subgraphs[nv.SubGraph()] = subgraph + applySubGraphStyle(subgraph) + } + item := subgraph.Node(name) + nv.Visualize(&item) + itemsByNode[node] = item + + } + + for fromNode, fromItem := range itemsByNode { + for _, toNode := range g.OutgoingEdges(fromNode) { + if toItem, ok := itemsByNode[toNode]; ok { + root.Edge(fromItem, toItem).Attr("color", "#949494") + } + } + } + + return root +} + +func applySubGraphStyle(graph *dot.Graph) { + graph.Attr("label", "") + graph.Attr("style", "rounded") + graph.Attr("bgcolor", "#E8E8E8") + graph.Attr("color", "lightgrey") + graph.Attr("fontname", "COURIER") + graph.Attr("fontcolor", "#46494C") +} diff --git a/di/internal/graphkv/output_test.go b/di/internal/graphkv/output_test.go new file mode 100644 index 0000000..2470a51 --- /dev/null +++ b/di/internal/graphkv/output_test.go @@ -0,0 +1 @@ +package graphkv diff --git a/di/internal/graphkv/sort.go b/di/internal/graphkv/sort.go new file mode 100644 index 0000000..e1ee1ce --- /dev/null +++ b/di/internal/graphkv/sort.go @@ -0,0 +1,170 @@ +package graphkv + +import ( + "errors" +) + +// Errors relating to the DFSSorter. +var ( + ErrCyclicGraph = errors.New("the graph cannot be cyclic") +) + +// DFSSorter topologically sorts a directed graph's nodes based on the +// directed edges between them using the Depth-first search algorithm. +type DFSSorter struct { + graph *directedGraph + sorted []Key + visiting map[Key]bool + discovered map[Key]bool +} + +// NewDFSSorter returns a new DFS sorter. +func NewDFSSorter(graph *directedGraph) *DFSSorter { + return &DFSSorter{ + graph: graph, + } +} + +func (s *DFSSorter) init() { + s.sorted = make([]Key, 0, s.graph.NodeCount()) + s.visiting = make(map[Key]bool) + s.discovered = make(map[Key]bool, s.graph.NodeCount()) +} + +// Sort returns the sorted nodes. +func (s *DFSSorter) Sort() ([]Key, error) { + s.init() + + // > while there are unmarked nodes do + for _, node := range s.graph.Nodes() { + if err := s.visit(node); err != nil { + return nil, err + } + } + + // as the nodes were appended to the slice for performance reasons, + // rather than prepended as correctly stated by the algorithm, + // we need to reverse the sorted slice + for i, j := 0, len(s.sorted)-1; i < j; i, j = i+1, j-1 { + s.sorted[i], s.sorted[j] = s.sorted[j], s.sorted[i] + } + + return s.sorted, nil +} + +// See https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search +func (s *DFSSorter) visit(node Key) error { + // > if n has a permanent mark then return + if discovered, ok := s.discovered[node]; ok && discovered { + return nil + } + // > if n has a temporary mark then stop (not a DAG) + if visiting, ok := s.visiting[node]; ok && visiting { + return ErrCyclicGraph + } + + // > mark n temporarily + s.visiting[node] = true + + // > for each node m with an edge from n to m do + for _, outgoing := range s.graph.OutgoingEdges(node) { + if err := s.visit(outgoing); err != nil { + return err + } + } + + s.discovered[node] = true + delete(s.visiting, node) + + s.sorted = append(s.sorted, node) + return nil +} + +// DFSSort returns the graph's nodes in topological order based on the +// directed edges between them using the Depth-first search algorithm. +func (g *directedGraph) DFSSort() ([]Key, error) { + sorter := NewDFSSorter(g) + return sorter.Sort() +} + +// Errors relating to the CoffmanGrahamSorter. +var ( + ErrDependencyOrder = errors.New("the topological dependency order is incorrect") +) + +// CoffmanGrahamSorter sorts a graph's nodes into a sequence of levels, +// arranging so that a node which comes after another in the order is +// assigned to a lower level, and that a level never exceeds the width. +// See https://en.wikipedia.org/wiki/Coffman–Graham_algorithm +type CoffmanGrahamSorter struct { + graph *directedGraph + width int +} + +// NewCoffmanGrahamSorter returns a new Coffman-Graham sorter. +func NewCoffmanGrahamSorter(graph *directedGraph, width int) *CoffmanGrahamSorter { + return &CoffmanGrahamSorter{ + graph: graph, + width: width, + } +} + +// Sort returns the sorted nodes. +func (s *CoffmanGrahamSorter) Sort() ([][]Key, error) { + // create a copy of the graph and remove transitive edges + reduced := s.graph.Copy() + reduced.RemoveTransitives() + + // topologically sort the graph nodes + nodes, err := reduced.DFSSort() + if err != nil { + return nil, err + } + + layers := make([][]Key, 0) + levels := make(map[Key]int, len(nodes)) + + for _, node := range nodes { + dependantLevel := -1 + for _, dependant := range reduced.IncomingEdges(node) { + level, ok := levels[dependant] + if !ok { + return nil, ErrDependencyOrder + } + if level > dependantLevel { + dependantLevel = level + } + } + + level := -1 + // find the first unfilled layer outgoing the dependent layer + // skip this if the dependent layer is the last + if dependantLevel < len(layers)-1 { + for i := dependantLevel + 1; i < len(layers); i++ { + // ensure the layer doesn't exceed the desired width + if len(layers[i]) < s.width { + level = i + break + } + } + } + // create a new layer new none was found + if level == -1 { + layers = append(layers, make([]Key, 0, 1)) + level = len(layers) - 1 + } + + layers[level] = append(layers[level], node) + levels[node] = level + } + + return layers, nil +} + +// CoffmanGrahamSort sorts the graph's nodes into a sequence of levels, +// arranging so that a node which comes after another in the order is +// assigned to a lower level, and that a level never exceeds the specified width. +func (g *directedGraph) CoffmanGrahamSort(width int) ([][]Key, error) { + sorter := NewCoffmanGrahamSorter(g, width) + return sorter.Sort() +} diff --git a/di/internal/graphkv/sort_test.go b/di/internal/graphkv/sort_test.go new file mode 100644 index 0000000..06baae8 --- /dev/null +++ b/di/internal/graphkv/sort_test.go @@ -0,0 +1,86 @@ +package graphkv + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDFSSorter(t *testing.T) { + graph := newDirectedGraph() + graph.AddNodes(0, 1, 2, 3, 4, 5, 6, 7) + graph.AddEdge(0, 2) + graph.AddEdge(1, 2) + graph.AddEdge(1, 5) + graph.AddEdge(1, 6) + graph.AddEdge(2, 5) + graph.AddEdge(3, 5) + graph.AddEdge(5, 6) + graph.AddEdge(5, 7) + + sorted, err := graph.DFSSort() + + assert.NoError(t, err, "graph.DFSSort() error should be nil") + assert.Equal(t, []Key{4, 3, 1, 0, 2, 5, 7, 6}, sorted, "graph.DFSSort() nodes should equal [4, 3, 1, 0, 2, 5, 7, 6]") +} + +func TestDFSSorterCyclic(t *testing.T) { + graph := newDirectedGraph() + graph.AddNodes(0, 1) + graph.AddEdge(0, 1) + graph.AddEdge(1, 0) + + sorted, err := graph.DFSSort() + + assert.EqualError(t, err, ErrCyclicGraph.Error(), "graph.DFSSort() error should be ErrCyclicGraph") + assert.Nil(t, sorted, "graph.DFSSort() nodes should be nil") +} + +func TestCoffmanGrahamSorter(t *testing.T) { + graph := newDirectedGraph() + + graph.AddNodes(0, 1, 2, 3, 4, 5, 6, 7, 8) + graph.AddEdge(0, 2) + graph.AddEdge(0, 5) + graph.AddEdge(1, 2) + graph.AddEdge(2, 3) + graph.AddEdge(2, 4) + graph.AddEdge(3, 6) + graph.AddEdge(4, 6) + graph.AddEdge(5, 7) + graph.AddEdge(6, 7) + graph.AddEdge(6, 8) + + sorted, err := graph.CoffmanGrahamSort(2) + + assert.NoError(t, err, "graph.CoffmanGrahamSort(2)0 error should be nil") + assert.Equal(t, [][]Key{ + []Key{1, 0}, + []Key{5, 2}, + []Key{4, 3}, + []Key{6}, + []Key{8, 7}, + }, sorted, "graph.CoffmanGrahamSort(2) nodes should equal [[1, 0], [5, 2], [4, 3], [6], [8, 7]]") +} + +func TestCoffmanGrahamSorterCyclic(t *testing.T) { + graph := newDirectedGraph() + + graph.AddNodes(0, 1, 2, 3, 4, 5, 6, 7, 8) + graph.AddEdge(0, 2) + graph.AddEdge(0, 5) + graph.AddEdge(1, 2) + graph.AddEdge(2, 0) // cyclic edge + graph.AddEdge(2, 3) + graph.AddEdge(2, 4) + graph.AddEdge(3, 6) + graph.AddEdge(4, 6) + graph.AddEdge(5, 7) + graph.AddEdge(6, 7) + graph.AddEdge(6, 8) + + sorted, err := graph.CoffmanGrahamSort(2) + + assert.EqualError(t, err, ErrCyclicGraph.Error(), "graph.CoffmanGrahamSort(2) error should be ErrCyclicGraph") + assert.Nil(t, sorted, "graph.CoffmanGrahamSort(2) nodes should be nil") +} diff --git a/di/internal/reflection/func.go b/di/internal/reflection/func.go new file mode 100644 index 0000000..1ec5894 --- /dev/null +++ b/di/internal/reflection/func.go @@ -0,0 +1,35 @@ +package reflection + +import ( + "fmt" + "reflect" + "runtime" +) + +// IsFunc +func IsFunc(value interface{}) bool { + return reflect.ValueOf(value).Kind() == reflect.Func +} + +// Func +type Func struct { + Name string + reflect.Type + reflect.Value +} + +// InspectFunction +func InspectFunction(fn interface{}) *Func { + if !IsFunc(fn) { + panic(fmt.Sprintf("%s: not a function", reflect.TypeOf(fn).Kind())) // todo: improve message + } + + val := reflect.ValueOf(fn) + fnpc := runtime.FuncForPC(val.Pointer()) + + return &Func{ + Name: fnpc.Name(), + Type: val.Type(), + Value: val, + } +} diff --git a/di/internal/reflection/iface.go b/di/internal/reflection/iface.go new file mode 100644 index 0000000..3ed5aea --- /dev/null +++ b/di/internal/reflection/iface.go @@ -0,0 +1,25 @@ +package reflection + +import ( + "fmt" + "reflect" +) + +// InspectInterfacePtr +func InspectInterfacePtr(iface interface{}) *Interface { + typ := reflect.TypeOf(iface) + if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Interface { + panic(fmt.Sprintf("%s: not a pointer to interface", typ)) // todo: improve message + } + + return &Interface{ + Name: typ.Elem().Name(), + Type: typ.Elem(), + } +} + +// Interface +type Interface struct { + Name string + Type reflect.Type +} diff --git a/di/internal/reflection/reflection.go b/di/internal/reflection/reflection.go new file mode 100644 index 0000000..030cfa6 --- /dev/null +++ b/di/internal/reflection/reflection.go @@ -0,0 +1,20 @@ +package reflection + +import "reflect" + +var errorInterface = reflect.TypeOf(new(error)).Elem() + +// IsError +func IsError(typ reflect.Type) bool { + return typ.Implements(errorInterface) +} + +// IsCleanup +func IsCleanup(typ reflect.Type) bool { + return typ.Kind() == reflect.Func && typ.NumIn() == 0 && typ.NumOut() == 0 +} + +// IsPtr +func IsPtr(value interface{}) bool { + return reflect.ValueOf(value).Kind() == reflect.Ptr +} diff --git a/di/invoker.go b/di/invoker.go new file mode 100644 index 0000000..559e0c4 --- /dev/null +++ b/di/invoker.go @@ -0,0 +1,78 @@ +package di + +import ( + "fmt" + "github.com/yoyofxteam/dependencyinjection/di/internal/reflection" + "reflect" +) + +type invokerType int + +const ( + invokerUnknown invokerType = iota + invokerStd // func (deps) {} + invokerError // func (deps) error {} +) + +func determineInvokerType(fn *reflection.Func) (invokerType, error) { + if fn.NumOut() == 0 { + return invokerStd, nil + } + if fn.NumOut() == 1 && reflection.IsError(fn.Out(0)) { + return invokerError, nil + } + return invokerUnknown, fmt.Errorf("the invoke function must be a function like `func([dep1, dep2, ...]) [error]`, got `%s`", fn.Type) +} + +type invoker struct { + typ invokerType + fn *reflection.Func +} + +func newInvoker(fn interface{}) (*invoker, error) { + if fn == nil { + return nil, fmt.Errorf("the invoke function must be a function like `func([dep1, dep2, ...]) [error]`, got `%s`", "nil") + } + if !reflection.IsFunc(fn) { + return nil, fmt.Errorf("the invoke function must be a function like `func([dep1, dep2, ...]) [error]`, got `%s`", reflect.ValueOf(fn).Type()) + } + ifn := reflection.InspectFunction(fn) + typ, err := determineInvokerType(ifn) + if err != nil { + return nil, err + } + return &invoker{ + typ: typ, + fn: reflection.InspectFunction(fn), + }, nil +} + +func (i *invoker) Invoke(c *Container) error { + plist := i.parameters() + values, err := plist.Resolve(c) + if err != nil { + return fmt.Errorf("could not resolve invoke parameters: %s", err) + } + results := i.fn.Call(values) + if len(results) == 0 { + return nil + } + if results[0].Interface() == nil { + return nil + } + return results[0].Interface().(error) +} + +func (i *invoker) parameters() parameterList { + var plist parameterList + for j := 0; j < i.fn.NumIn(); j++ { + ptype := i.fn.In(j) + p := parameter{ + res: ptype, + optional: false, + embed: isEmbedParameter(ptype), + } + plist = append(plist, p) + } + return plist +} diff --git a/di/key.go b/di/key.go new file mode 100644 index 0000000..f0a3cf7 --- /dev/null +++ b/di/key.go @@ -0,0 +1,62 @@ +package di + +import ( + "fmt" + "reflect" + + "github.com/emicklei/dot" +) + +// key is a id of provider in container +type key struct { + name string + res reflect.Type + typ providerType +} + +// String represent resultKey as string. +func (k key) String() string { + if k.name == "" { + return fmt.Sprintf("%s", k.res) + } + return fmt.Sprintf("%s[%s]", k.res, k.name) +} + +// IsAlwaysVisible +func (k key) IsAlwaysVisible() bool { + return k.typ == ptConstructor +} + +// Package +func (k key) SubGraph() string { + var pkg string + switch k.res.Kind() { + case reflect.Slice, reflect.Ptr: + pkg = k.res.Elem().PkgPath() + default: + pkg = k.res.PkgPath() + } + + return pkg +} + +// Visualize +func (k key) Visualize(node *dot.Node) { + node.Label(k.String()) + node.Attr("fontname", "COURIER") + node.Attr("style", "filled") + node.Attr("fontcolor", "white") + switch k.typ { + case ptConstructor: + node.Attr("shape", "box") + node.Attr("color", "#46494C") + case ptGroup: + node.Attr("shape", "doubleoctagon") + node.Attr("color", "#E54B4B") + case ptInterface: + node.Attr("color", "#2589BD") + case ptEmbedParameter: + node.Attr("shape", "box") + node.Attr("color", "#E5984B") + } +} diff --git a/di/options.go b/di/options.go new file mode 100644 index 0000000..05437e7 --- /dev/null +++ b/di/options.go @@ -0,0 +1,64 @@ +package di + +// ExtractOption +type ProvideOption interface { + apply(params *ProvideParams) +} + +type provideOption func(params *ProvideParams) + +func (o provideOption) apply(params *ProvideParams) { + o(params) +} + +// ProvideParams is a `Provide()` method options. Name is a unique identifier of type instance. Provider is a constructor +// function. Interfaces is a interface that implements a provider result type. +type ProvideParams struct { + Name string + Interfaces []interface{} + Parameters ParameterBag + IsPrototype bool +} + +func (p ProvideParams) apply(params *ProvideParams) { + *params = p +} + +// As +func As(interfaces ...interface{}) ProvideOption { + return provideOption(func(params *ProvideParams) { + params.Interfaces = append(params.Interfaces, interfaces...) + }) +} + +// InvokeParams is a invoke parameters. +type InvokeParams struct{} + +func (p InvokeParams) apply(params *InvokeParams) { + *params = p +} + +// InvokeOption +type InvokeOption interface { + apply(params *InvokeParams) +} + +// ExtractParams +type ExtractParams struct { + Name string +} + +func (p ExtractParams) apply(params *ExtractParams) { + *params = p +} + +// ExtractOption +type ExtractOption interface { + apply(params *ExtractParams) +} + +type extractOption func(params *ExtractParams) + +func (o extractOption) apply(params *ExtractParams) { + o(params) +} diff --git a/di/panic.go b/di/panic.go new file mode 100644 index 0000000..15d7f2e --- /dev/null +++ b/di/panic.go @@ -0,0 +1,7 @@ +package di + +import "fmt" + +func panicf(format string, a ...interface{}) { + panic(fmt.Sprintf(format, a...)) +} diff --git a/di/parameter.go b/di/parameter.go new file mode 100644 index 0000000..d8751b3 --- /dev/null +++ b/di/parameter.go @@ -0,0 +1,75 @@ +package di + +import ( + "reflect" +) + +// Parameter +type Parameter struct { + internalParameter +} + +// parameterRequired +type parameter struct { + name string + res reflect.Type + optional bool + embed bool +} + +func (p parameter) String() string { + return key{name: p.name, res: p.res}.String() +} + +// ResolveProvider resolves parameter provider +func (p parameter) ResolveProvider(c *Container) (internalProvider, bool) { + for _, pt := range providerLookupSequence { + k := key{ + name: p.name, + res: p.res, + typ: pt, + } + if !c.graph.Exists(k) { + continue + } + node := c.graph.Get(k) + return node.Value.(internalProvider), true + } + return nil, false +} + +func (p parameter) ResolveValue(c *Container) (reflect.Value, error) { + provider, exists := p.ResolveProvider(c) + if !exists && p.optional { + return reflect.New(p.res).Elem(), nil + } + if !exists { + return reflect.Value{}, ErrParameterProviderNotFound{param: p} + } + pl := provider.ParameterList() + values, err := pl.Resolve(c) + if err != nil { + return reflect.Value{}, err + } + value, cleanup, err := provider.Provide(values...) + if err != nil { + return value, ErrParameterProvideFailed{k: provider.Key(), err: err} + } + if cleanup != nil { + c.cleanups = append(c.cleanups, cleanup) + } + return value, nil +} + +// isEmbedParameter +func isEmbedParameter(typ reflect.Type) bool { + return typ.Kind() == reflect.Struct && typ.Implements(parameterInterface) +} + +// internalParameter +type internalParameter interface { + isDependencyInjectionParameter() +} + +// parameterInterface +var parameterInterface = reflect.TypeOf(new(internalParameter)).Elem() diff --git a/di/parameter_bag.go b/di/parameter_bag.go new file mode 100644 index 0000000..84368af --- /dev/null +++ b/di/parameter_bag.go @@ -0,0 +1,98 @@ +package di + +import ( + "fmt" + "reflect" +) + +// createParameterBugProvider +func createParameterBugProvider(key key, parameters ParameterBag) internalProvider { + return newProviderConstructor(key.String(), func() ParameterBag { return parameters }) +} + +// parameterBagType +var parameterBagType = reflect.TypeOf(ParameterBag{}) + +// ParameterBag +type ParameterBag map[string]interface{} + +// Exists +func (b ParameterBag) Exists(key string) bool { + _, ok := b[key] + return ok +} + +// Get +func (b ParameterBag) Get(key string) (interface{}, bool) { + value, ok := b[key] + return value, ok +} + +// String +func (b ParameterBag) String(key string) (string, bool) { + value, ok := b[key].(string) + return value, ok +} + +// Int64 +func (b ParameterBag) Int64(key string) (int64, bool) { + value, ok := b[key].(int64) + return value, ok +} + +// Int +func (b ParameterBag) Int(key string) (int, bool) { + value, ok := b[key].(int) + return value, ok +} + +// Float64 +func (b ParameterBag) Float64(key string) (float64, bool) { + value, ok := b[key].(float64) + return value, ok +} + +// Require +func (b ParameterBag) Require(key string) interface{} { + value, ok := b[key] + if !ok { + panic(fmt.Sprintf("value for string key `%s` not found", key)) + } + return value +} + +// RequireString +func (b ParameterBag) RequireString(key string) string { + value, ok := b[key].(string) + if !ok { + panic(fmt.Sprintf("value for string key `%s` not found", key)) + } + return value +} + +// RequireInt64 +func (b ParameterBag) RequireInt64(key string) int64 { + value, ok := b[key].(int64) + if !ok { + panic(fmt.Sprintf("value for string key `%s` not found", key)) + } + return value +} + +// RequireInt +func (b ParameterBag) RequireInt(key string) int { + value, ok := b[key].(int) + if !ok { + panic(fmt.Sprintf("value for string key `%s` not found", key)) + } + return value +} + +// RequireFloat64 +func (b ParameterBag) RequireFloat64(key string) float64 { + value, ok := b[key].(float64) + if !ok { + panic(fmt.Sprintf("value for string key `%s` not found", key)) + } + return value +} diff --git a/di/parameter_bag_test.go b/di/parameter_bag_test.go new file mode 100644 index 0000000..fa3c50e --- /dev/null +++ b/di/parameter_bag_test.go @@ -0,0 +1,142 @@ +package di + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParameterBag_Get(t *testing.T) { + t.Run("key exists", func(t *testing.T) { + pb := ParameterBag{ + "get": "get", + } + + v, ok := pb.Get("get") + require.Equal(t, "get", v) + require.True(t, ok) + }) + + t.Run("key not exists", func(t *testing.T) { + pb := ParameterBag{} + + v, ok := pb.Get("get") + require.Equal(t, nil, v) + require.False(t, ok) + }) +} + +func TestParameterBag_GetType(t *testing.T) { + t.Run("key exists", func(t *testing.T) { + pb := ParameterBag{ + "string": "string", + "int64": int64(64), + "int": int(64), + "float64": float64(64), + } + + s, ok := pb.String("string") + require.Equal(t, "string", s) + require.True(t, ok) + + i64, ok := pb.Int64("int64") + require.Equal(t, int64(64), i64) + require.True(t, ok) + + i, ok := pb.Int("int") + require.Equal(t, int(64), i) + require.True(t, ok) + + f64, ok := pb.Float64("float64") + require.Equal(t, float64(64), f64) + require.True(t, ok) + }) + + t.Run("key not exists", func(t *testing.T) { + pb := ParameterBag{} + + s, ok := pb.String("string") + require.Equal(t, "", s) + require.False(t, ok) + + i64, ok := pb.Int64("int64") + require.Equal(t, int64(0), i64) + require.False(t, ok) + + i, ok := pb.Int("int") + require.Equal(t, int(0), i) + require.False(t, ok) + + f64, ok := pb.Float64("float64") + require.Equal(t, float64(0), f64) + require.False(t, ok) + }) +} + +func TestParameterBag_Exists(t *testing.T) { + pb := ParameterBag{} + + require.False(t, pb.Exists("not existing key")) +} + +func TestParameterBag_Require(t *testing.T) { + t.Run("key exists", func(t *testing.T) { + pb := ParameterBag{ + "require": "require", + } + + value := pb.Require("require") + require.Equal(t, "require", value) + }) + + t.Run("key not exists", func(t *testing.T) { + pb := ParameterBag{} + + require.PanicsWithValue(t, "value for string key `not existing key` not found", func() { + pb.Require("not existing key") + }) + }) +} + +func TestParameterBag_RequireTypes(t *testing.T) { + t.Run("key exists", func(t *testing.T) { + pb := ParameterBag{ + "string": "string", + "int64": int64(64), + "int": int(64), + "float64": float64(64), + } + + s := pb.RequireString("string") + require.Equal(t, "string", s) + + i64 := pb.RequireInt64("int64") + require.Equal(t, int64(64), i64) + + i := pb.RequireInt("int") + require.Equal(t, int(64), i) + + f64 := pb.RequireFloat64("float64") + require.Equal(t, float64(64), f64) + }) + + t.Run("key not exists", func(t *testing.T) { + pb := ParameterBag{} + + require.PanicsWithValue(t, "value for string key `string` not found", func() { + pb.RequireString("string") + }) + + require.PanicsWithValue(t, "value for string key `int64` not found", func() { + pb.RequireInt64("int64") + }) + + require.PanicsWithValue(t, "value for string key `int` not found", func() { + pb.RequireInt("int") + }) + + require.PanicsWithValue(t, "value for string key `float64` not found", func() { + pb.RequireFloat64("float64") + }) + }) +} diff --git a/di/parameter_list.go b/di/parameter_list.go new file mode 100644 index 0000000..36c710f --- /dev/null +++ b/di/parameter_list.go @@ -0,0 +1,20 @@ +package di + +import "reflect" + +// parameterList +type parameterList []parameter + +// ResolveValues loads all parameters presented in parameter list. +func (pl parameterList) Resolve(c *Container) ([]reflect.Value, error) { + var values []reflect.Value + for _, p := range pl { + value, err := p.ResolveValue(c) + if err != nil { + return nil, err + } + values = append(values, value) + } + + return values, nil +} diff --git a/di/provider.go b/di/provider.go new file mode 100644 index 0000000..594f05e --- /dev/null +++ b/di/provider.go @@ -0,0 +1,27 @@ +package di + +import "reflect" + +// provider lookup sequence +var providerLookupSequence = []providerType{ptConstructor, ptInterface, ptGroup, ptEmbedParameter} + +// providerType +type providerType int + +const ( + ptUnknown providerType = iota + ptConstructor + ptInterface + ptGroup + ptEmbedParameter +) + +// provider +type internalProvider interface { + // The identity of result type. + Key() key + // ParameterList returns array of dependencies. + ParameterList() parameterList + // Provide provides value from provided parameters. + Provide(values ...reflect.Value) (reflect.Value, func(), error) +} diff --git a/di/provider_ctor.go b/di/provider_ctor.go new file mode 100644 index 0000000..5c267d9 --- /dev/null +++ b/di/provider_ctor.go @@ -0,0 +1,127 @@ +package di + +import ( + "errors" + "fmt" + "github.com/yoyofxteam/dependencyinjection/di/internal/reflection" + "reflect" +) + +type ctorType int + +const ( + ctorUnknown ctorType = iota // unknown ctor signature + ctorStd // (deps) (result) + ctorError // (deps) (result, error) + ctorCleanup // (deps) (result, cleanup) + ctorCleanupError // (deps) (result, cleanup, error) +) + +// newProviderConstructor +func newProviderConstructor(name string, ctor interface{}) *providerConstructor { + if ctor == nil { + panicf("The constructor must be a function like `func([dep1, dep2, ...]) (, [cleanup, error])`, got `%s`", "nil") + } + if !reflection.IsFunc(ctor) { + panicf("The constructor must be a function like `func([dep1, dep2, ...]) (, [cleanup, error])`, got `%s`", reflect.ValueOf(ctor).Type()) + } + fn := reflection.InspectFunction(ctor) + ctorType := determineCtorType(fn) + return &providerConstructor{ + name: name, + ctor: fn, + ctorType: ctorType, + } +} + +// providerConstructor +type providerConstructor struct { + name string + ctor *reflection.Func + ctorType ctorType + clean *reflection.Func +} + +func (c providerConstructor) Key() key { + return key{ + name: c.name, + res: c.ctor.Out(0), + typ: ptConstructor, + } +} + +func (c providerConstructor) ParameterList() parameterList { + var plist parameterList + for i := 0; i < c.ctor.NumIn(); i++ { + ptype := c.ctor.In(i) + var name string + if ptype == parameterBagType { + name = c.Key().String() + } + p := parameter{ + name: name, + res: ptype, + optional: false, + embed: isEmbedParameter(ptype), + } + plist = append(plist, p) + } + return plist +} + +// Provide +func (c *providerConstructor) Provide(values ...reflect.Value) (reflect.Value, func(), error) { + out := callResult(c.ctor.Call(values)) + switch c.ctorType { + case ctorStd: + return out.instance(), nil, nil + case ctorError: + return out.instance(), nil, out.error(1) + case ctorCleanup: + return out.instance(), out.cleanup(), nil + case ctorCleanupError: + return out.instance(), out.cleanup(), out.error(2) + } + return reflect.Value{}, nil, errors.New("you found a bug, please create new issue for " + + "this: https://github.com/defval/inject/issues/new") +} + +// determineCtorType +func determineCtorType(fn *reflection.Func) ctorType { + if fn.NumOut() == 1 { + return ctorStd + } + if fn.NumOut() == 2 { + if reflection.IsError(fn.Out(1)) { + return ctorError + } + if reflection.IsCleanup(fn.Out(1)) { + return ctorCleanup + } + } + if fn.NumOut() == 3 && reflection.IsCleanup(fn.Out(1)) && reflection.IsError(fn.Out(2)) { + return ctorCleanupError + } + panic(fmt.Sprintf("The constructor must be a function like `func([dep1, dep2, ...]) (, [cleanup, error])`, got `%s`", fn.Name)) +} + +// callResult +type callResult []reflect.Value + +func (r callResult) instance() reflect.Value { + return r[0] +} + +func (r callResult) cleanup() func() { + if r[1].IsNil() { + return nil + } + return r[1].Interface().(func()) +} + +func (r callResult) error(position int) error { + if r[position].IsNil() { + return nil + } + return r[position].Interface().(error) +} diff --git a/di/provider_embed.go b/di/provider_embed.go new file mode 100644 index 0000000..2183027 --- /dev/null +++ b/di/provider_embed.go @@ -0,0 +1,96 @@ +package di + +import ( + "reflect" + "strings" +) + +// createStructProvider +func newProviderEmbed(p parameter) *providerEmbed { + var embedType reflect.Type + if p.res.Kind() == reflect.Ptr { + embedType = p.res.Elem() + } else { + embedType = p.res + } + + return &providerEmbed{ + key: key{ + name: p.name, + res: p.res, + typ: ptEmbedParameter, + }, + embedType: embedType, + embedValue: reflect.New(embedType).Elem(), + } +} + +type providerEmbed struct { + key key + embedType reflect.Type + embedValue reflect.Value +} + +func (p *providerEmbed) Key() key { + return p.key +} + +func (p *providerEmbed) ParameterList() parameterList { + var plist parameterList + for i := 0; i < p.embedType.NumField(); i++ { + name, optional, isDependency := p.inspectFieldTag(i) + if !isDependency { + continue + } + field := p.embedType.Field(i) + plist = append(plist, parameter{ + name: name, + res: field.Type, + optional: optional, + embed: isEmbedParameter(field.Type), + }) + } + return plist +} + +func (p *providerEmbed) Provide(values ...reflect.Value) (reflect.Value, func(), error) { + for i, offset := 0, 0; i < p.embedType.NumField(); i++ { + _, _, isDependency := p.inspectFieldTag(i) + if !isDependency { + offset++ + continue + } + + p.embedValue.Field(i).Set(values[i-offset]) + } + + return p.embedValue, nil, nil +} + +func (p *providerEmbed) inspectFieldTag(num int) (name string, optional bool, isDependency bool) { + fieldType := p.embedType.Field(num) + fieldValue := p.embedValue.Field(num) + tag, tagExists := fieldType.Tag.Lookup("di") + if !tagExists || !fieldValue.CanSet() { + return "", false, false + } + name, optional = p.parseTag(tag) + return name, optional, true +} + +func (p *providerEmbed) parseTag(tag string) (name string, optional bool) { + options := strings.Split(tag, ",") + if len(options) == 0 { + return "", false + } + if len(options) == 1 && options[0] == "optional" { + return "", true + } + if len(options) == 1 { + return options[0], false + } + if len(options) == 2 && options[1] == "optional" { + return options[0], true + } + panic("incorrect di tag") +} diff --git a/di/provider_group.go b/di/provider_group.go new file mode 100644 index 0000000..a3bdcb0 --- /dev/null +++ b/di/provider_group.go @@ -0,0 +1,50 @@ +package di + +import ( + "reflect" +) + +// newProviderGroup creates new group from provided resultKey. +func newProviderGroup(k key) *providerGroup { + ifaceKey := key{ + res: reflect.SliceOf(k.res), + typ: ptGroup, + } + + return &providerGroup{ + result: ifaceKey, + pl: parameterList{}, + } +} + +// providerGroup +type providerGroup struct { + result key + pl parameterList +} + +// Add +func (i *providerGroup) Add(k key) { + i.pl = append(i.pl, parameter{ + name: k.name, + res: k.res, + optional: false, + embed: false, + }) +} + +// resultKey +func (i providerGroup) Key() key { + return i.result +} + +// parameters +func (i providerGroup) ParameterList() parameterList { + return i.pl +} + +// Provide +func (i providerGroup) Provide(values ...reflect.Value) (reflect.Value, func(), error) { + group := reflect.New(i.result.res).Elem() + return reflect.Append(group, values...), nil, nil +} diff --git a/di/provider_iface.go b/di/provider_iface.go new file mode 100644 index 0000000..0648057 --- /dev/null +++ b/di/provider_iface.go @@ -0,0 +1,47 @@ +package di + +import ( + "github.com/yoyofxteam/dependencyinjection/di/internal/reflection" + "reflect" +) + +// newProviderInterface +func newProviderInterface(provider internalProvider, as interface{}) *providerInterface { + iface := reflection.InspectInterfacePtr(as) + if !provider.Key().res.Implements(iface.Type) { + panicf("%s not implement %s", provider.Key(), iface.Type) + } + return &providerInterface{ + res: key{ + name: provider.Key().name, + res: iface.Type, + typ: ptInterface, + }, + provider: provider, + } +} + +// providerInterface +type providerInterface struct { + res key + provider internalProvider +} + +func (i *providerInterface) Key() key { + return i.res +} + +func (i *providerInterface) ParameterList() parameterList { + var plist parameterList + plist = append(plist, parameter{ + name: i.provider.Key().name, + res: i.provider.Key().res, + optional: false, + embed: false, + }) + return plist +} + +func (i *providerInterface) Provide(values ...reflect.Value) (reflect.Value, func(), error) { + return values[0], nil, nil +} diff --git a/di/provider_stub.go b/di/provider_stub.go new file mode 100644 index 0000000..1ad349a --- /dev/null +++ b/di/provider_stub.go @@ -0,0 +1,29 @@ +package di + +import ( + "fmt" + "reflect" +) + +// providerStub +type providerStub struct { + msg string + res key +} + +// newProviderStub +func newProviderStub(k key, msg string) *providerStub { + return &providerStub{res: k, msg: msg} +} + +func (m *providerStub) Key() key { + return m.res +} + +func (m *providerStub) ParameterList() parameterList { + return parameterList{} +} + +func (m *providerStub) Provide(values ...reflect.Value) (reflect.Value, func(), error) { + return reflect.Value{}, nil, fmt.Errorf(m.msg) +} diff --git a/di/singleton.go b/di/singleton.go new file mode 100644 index 0000000..d14cba1 --- /dev/null +++ b/di/singleton.go @@ -0,0 +1,27 @@ +package di + +import ( + "reflect" +) + +// asSingleton creates a singleton wrapper. +func asSingleton(provider internalProvider) *singletonWrapper { + return &singletonWrapper{internalProvider: provider} +} + +// singletonWrapper is a embedParamProvider wrapper. Stores provided value for prevent reinitialization. +type singletonWrapper struct { + internalProvider // source provider + value reflect.Value // value cache +} + +// Provide +func (s *singletonWrapper) Provide(values ...reflect.Value) (reflect.Value, func(), error) { + if s.value.IsValid() { + return s.value, nil, nil + } + value, cleanup, err := s.internalProvider.Provide(values...) + s.value = value + + return value, cleanup, err +} diff --git a/di_test.go b/di_test.go index bff2c61..048bf0e 100644 --- a/di_test.go +++ b/di_test.go @@ -1,13 +1,11 @@ package dependencyinjection import ( - "github.com/goava/di" "github.com/magiconair/properties/assert" "math/rand" "strconv" "testing" "time" - "unsafe" ) type A struct { @@ -39,11 +37,3 @@ func Test_DI_Register(t *testing.T) { _ = serviceProvider.GetService(&env) assert.Equal(t, env.Name, ls) } - -func Test_MapConvertTags(t *testing.T) { - tags := map[string]string{"name": "A1"} - p := unsafe.Pointer(&tags) - var tag di.Tags - tag = *(*di.Tags)(p) - assert.Equal(t, tag != nil, true) -} diff --git a/go.mod b/go.mod index b3c4d66..e597a28 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.16 require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/goava/di v1.10.0 + github.com/emicklei/dot v0.10.1 github.com/magiconair/properties v1.8.5 - github.com/stretchr/testify v1.6.1 + github.com/stretchr/testify v1.6.1 // indirect github.com/yoyofxteam/reflectx v0.2.3 ) diff --git a/graph.png b/graph.png new file mode 100644 index 0000000000000000000000000000000000000000..422ce67b043bee798a55ef803bab9dddf2133381 GIT binary patch literal 134199 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 literal 0 HcmV?d00001 diff --git a/options.go b/options.go new file mode 100644 index 0000000..6caac4f --- /dev/null +++ b/options.go @@ -0,0 +1,180 @@ +package dependencyinjection + +import "github.com/yoyofxteam/dependencyinjection/di" + +// OPTIONS + +// Option configures container. See inject.Provide(), inject.Bundle(), inject.Replace(). +type Option interface { + apply(*Container) +} + +// Provide returns container option that explains how to create an instance of a type inside a container. +// +// The first argument is the constructor function. A constructor is a function that creates an instance of the required +// type. It can take an unlimited number of arguments needed to create an instance - the first returned value. +// +// func NewServer(mux *http.ServeMux) *http.Server { +// return &http.Server{ +// Handle: mux, +// } +// } +// +// Optionally, you can return a cleanup function and initializing error. +// +// func NewServer(mux *http.ServeMux) (*http.Server, cleanup func(), err error) { +// if time.Now().Day = 1 { +// return nil, nil, errors.New("the server is down on the first day of a month") +// } +// +// server := &http.Server{ +// Handler: mux, +// } +// +// cleanup := func() { +// _ = server.Close() +// } +// +// return server, cleanup, nil +// } +// +// Other function signatures will cause error. +func Provide(provider interface{}, options ...ProvideOption) Option { + return option(func(container *Container) { + // todo: add provider + var params = di.ProvideParams{ + Parameters: map[string]interface{}{}, + } + + for _, opt := range options { + opt.apply(¶ms) + } + container.providers = append(container.providers, provide{ + provider: provider, + params: params, + }) + }) +} + +// Bundle group together container options. +// +// accountBundle := inject.Bundle( +// inject.Provide(NewAccountController), +// inject.Provide(NewAccountRepository), +// ) +// +// authBundle := inject.Bundle( +// inject.Provide(NewAuthController), +// inject.Provide(NewAuthRepository), +// ) +// +// container, _ := New( +// accountBundle, +// authBundle, +// ) +func Bundle(options ...Option) Option { + return option(func(container *Container) { + for _, opt := range options { + opt.apply(container) + } + }) +} + +// ProvideOption modifies default provide behavior. See inject.WithName(), inject.As(), inject.Prototype(). +type ProvideOption interface { + apply(params *di.ProvideParams) +} + +// WithName sets string identifier for provided value. +// +// inject.Provide(&http.Server{}, inject.WithName("first")) +// inject.Provide(&http.Server{}, inject.WithName("second")) +// +// container.Extract(&server, inject.Name("second")) +func WithName(name string) ProvideOption { + return provideOption(func(provider *di.ProvideParams) { + provider.Name = name + }) +} + +// As specifies interfaces that implement provider instance. Provide with As() automatically checks that constructor +// result implements interface and creates slice group with it. +// +// Provide(&http.ServerMux{}, inject.As(new(http.Handler))) +// +// var handler http.Handler +// container.Extract(&handler) // extract as interface +// +// var handlers []http.Handler +// container.Extract(&handlers) // extract group +func As(ifaces ...interface{}) ProvideOption { + return provideOption(func(provider *di.ProvideParams) { + provider.Interfaces = append(provider.Interfaces, ifaces...) + + }) +} + +// Prototype modifies Provide() behavior. By default, each type resolves as a singleton. This option sets that +// each type resolving creates a new instance of the type. +// +// Provide(&http.Server{], inject.Prototype()) +// +// var server1 *http.Server +// var server2 *http.Server +// container.Extract(&server1, &server2) +func Prototype() ProvideOption { + return provideOption(func(provider *di.ProvideParams) { + provider.IsPrototype = true + }) +} + +// ParameterBag is a provider parameter bag. It stores a construction parameters. It is a alternative way to +// configure type. +// +// inject.Provide(NewServer, inject.ParameterBag{ +// "addr": ":8080", +// }) +// +// NewServer(pb inject.ParameterBag) *http.Server { +// return &http.Server{ +// Addr: pb.RequireString("addr"), +// } +// } +type ParameterBag map[string]interface{} + +func (p ParameterBag) apply(provider *di.ProvideParams) { + for k, v := range p { + provider.Parameters[k] = v + } +} + +// ExtractOption modifies default extract behavior. See inject.Name(). +type ExtractOption interface { + apply(params *di.ExtractParams) +} + +// EXTRACT OPTIONS. + +// Name specify definition name. +func Name(name string) ExtractOption { + return extractOption(func(eo *di.ExtractParams) { + eo.Name = name + }) +} + +type option func(container *Container) + +func (o option) apply(container *Container) { o(container) } + +type provideOption func(provider *di.ProvideParams) + +func (o provideOption) apply(provider *di.ProvideParams) { o(provider) } + +type extractOption func(eo *di.ExtractParams) + +func (o extractOption) apply(eo *di.ExtractParams) { o(eo) } + +type extractOptions struct { + name string + target interface{} +} diff --git a/options_test.go b/options_test.go new file mode 100644 index 0000000..899fd32 --- /dev/null +++ b/options_test.go @@ -0,0 +1,50 @@ +package dependencyinjection + +import ( + "github.com/yoyofxteam/dependencyinjection/di" + "net/http" + "testing" + + "github.com/stretchr/testify/require" +) + +// TestProvideOptions +func TestProvideOptions(t *testing.T) { + opts := &di.ProvideParams{ + Parameters: map[string]interface{}{}, + } + + for _, opt := range []ProvideOption{ + WithName("test"), + As(new(http.Handler)), + Prototype(), + ParameterBag{ + "test": "test", + }, + } { + opt.apply(opts) + } + + require.Equal(t, &di.ProvideParams{ + Name: "test", + Interfaces: []interface{}{new(http.Handler)}, + IsPrototype: true, + Parameters: map[string]interface{}{ + "test": "test", + }, + }, opts) +} + +func TestExtractOptions(t *testing.T) { + opts := &di.ExtractParams{} + + for _, opt := range []ExtractOption{ + Name("test"), + } { + opt.apply(opts) + } + + require.Equal(t, &di.ExtractParams{ + Name: "test", + }, opts) +} diff --git a/serviceprovider.go b/serviceprovider.go index a263f33..d60470c 100644 --- a/serviceprovider.go +++ b/serviceprovider.go @@ -3,7 +3,6 @@ package dependencyinjection type IServiceProvider interface { GetService(refObject interface{}) error GetServiceByName(refObject interface{}, name string) error - GetServiceByTags(refObject interface{}, tags map[string]string) (err error) GetGraph() string InvokeService(fn interface{}) error }