├── .github └── workflows │ ├── golangci-lint.yml │ ├── main.yml │ └── semanticore.yml ├── .gitignore ├── .golangci.yml ├── Changelog.md ├── LICENSE ├── Readme.md ├── binding.go ├── binding_test.go ├── circular_test.go ├── dingo.go ├── dingo_child_test.go ├── dingo_setup_test.go ├── dingo_test.go ├── example ├── application │ └── service.go ├── main.go └── paypal │ ├── module.go │ └── processor.go ├── go.mod ├── go.sum ├── inspect.go ├── miniexample ├── logger │ └── service.go └── main.go ├── module.go ├── module_test.go ├── multi_dingo_test.go ├── renovate.json ├── scope.go └── scope_test.go /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | - main 9 | pull_request: 10 | permissions: 11 | contents: read 12 | pull-requests: read 13 | jobs: 14 | golangci: 15 | name: lint 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/setup-go@v5 19 | with: 20 | go-version: 1.* 21 | - uses: actions/checkout@v4 22 | with: 23 | fetch-depth: '0' 24 | - name: golangci-lint 25 | uses: golangci/golangci-lint-action@v8 26 | with: 27 | version: v2.1 28 | only-new-issues: true 29 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | tests: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | go: [ '1.23', '1.*' ] 17 | name: Tests 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Setup Go 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version: ${{ matrix.go }} 24 | - name: Get dependencies 25 | run: go get -v -t -d ./... 26 | - name: Test 27 | run: CGO_ENABLED=1 go test -race ./... 28 | coverage: 29 | runs-on: ubuntu-latest 30 | strategy: 31 | matrix: 32 | go: [ '1.*' ] 33 | name: Coverage Report 34 | steps: 35 | - uses: actions/checkout@v4 36 | - name: Setup Go 37 | uses: actions/setup-go@v5 38 | with: 39 | go-version: ${{ matrix.go }} 40 | - name: Get dependencies 41 | run: go get -v -t -d ./... 42 | - name: Test 43 | run: | 44 | CGO_ENABLED=1 go run gotest.tools/gotestsum@v1.7.0 --junitfile test.xml --format testname -- -coverprofile coverage.txt -race ./... 45 | go run github.com/boumenot/gocover-cobertura@v1.2.0 -ignore-gen-files -by-files < coverage.txt > coverage.xml 46 | - name: Code Coverage Summary Report 47 | uses: irongut/CodeCoverageSummary@v1.3.0 48 | with: 49 | filename: coverage.xml 50 | badge: true 51 | fail_below_min: false 52 | format: markdown 53 | hide_branch_rate: true 54 | hide_complexity: true 55 | indicators: true 56 | output: both 57 | thresholds: '50 75' 58 | - name: Add Coverage PR Comment 59 | uses: marocchino/sticky-pull-request-comment@v2 60 | if: github.event_name == 'pull_request' 61 | with: 62 | recreate: true 63 | path: code-coverage-results.md 64 | static-checks: 65 | runs-on: ubuntu-latest 66 | strategy: 67 | matrix: 68 | go: [ '1.*' ] 69 | name: Static checks 70 | steps: 71 | - uses: actions/checkout@v4 72 | - name: Setup Go 73 | uses: actions/setup-go@v5 74 | with: 75 | go-version: ${{ matrix.go }} 76 | - name: Get dependencies 77 | run: go get -v -t -d ./... 78 | - name: Go Vet 79 | run: go vet ./... 80 | - name: Go Fmt 81 | run: | 82 | fmt=$(gofmt -l .) 83 | test -z $fmt || (echo "please run gofmt" ; echo $fmt ; exit 1) 84 | - name: Goimports 85 | run: | 86 | go run golang.org/x/tools/cmd/goimports@latest -w . 87 | git diff --quiet || (echo 'goimports requires code cleanup:' ; git diff ; exit 1) 88 | - name: Go Generate 89 | run: | 90 | go generate ./... 91 | git diff --quiet || (echo 'generated go files are not up-to-date, check go generate, go.sum and go.mod:' ; git diff ; exit 1) 92 | -------------------------------------------------------------------------------- /.github/workflows/semanticore.yml: -------------------------------------------------------------------------------- 1 | name: Semanticore 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | jobs: 8 | semanticore: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | go: [ '1.*' ] 13 | name: Semanticore 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | - name: Setup Go 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version: ${{ matrix.go }} 22 | - name: Semanticore 23 | run: go run github.com/aoepeople/semanticore@main 24 | env: 25 | SEMANTICORE_TOKEN: ${{secrets.GITHUB_TOKEN}} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | .idea 3 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | concurrency: 4 4 | modules-download-mode: readonly 5 | tests: true 6 | allow-parallel-runners: true 7 | output: 8 | formats: 9 | text: 10 | path: stdout 11 | print-linter-name: true 12 | print-issued-lines: true 13 | linters: 14 | default: none 15 | enable: 16 | - bidichk 17 | - bodyclose 18 | - containedctx 19 | - contextcheck 20 | - copyloopvar 21 | - cyclop 22 | - durationcheck 23 | - err113 24 | - errcheck 25 | - errname 26 | - errorlint 27 | - exhaustive 28 | - forbidigo 29 | - forcetypeassert 30 | - gocognit 31 | - goconst 32 | - gocritic 33 | - gomoddirectives 34 | - gosec 35 | - govet 36 | - grouper 37 | - ineffassign 38 | - makezero 39 | - misspell 40 | - mnd 41 | - nakedret 42 | - nestif 43 | - nilerr 44 | - nilnil 45 | - noctx 46 | - nolintlint 47 | - nosprintfhostport 48 | - paralleltest 49 | - prealloc 50 | - predeclared 51 | - revive 52 | - staticcheck 53 | - testpackage 54 | - thelper 55 | - tparallel 56 | - unconvert 57 | - unparam 58 | - unused 59 | - usestdlibvars 60 | - usetesting 61 | - varnamelen 62 | - wrapcheck 63 | - wsl 64 | settings: 65 | mnd: 66 | ignored-functions: 67 | - context.WithTimeout 68 | nolintlint: 69 | require-explanation: true 70 | require-specific: true 71 | revive: 72 | rules: 73 | - name: var-naming 74 | disabled: true 75 | varnamelen: 76 | max-distance: 10 77 | ignore-names: 78 | - err 79 | - id 80 | ignore-type-assert-ok: true 81 | ignore-map-index-ok: true 82 | ignore-chan-recv-ok: true 83 | ignore-decls: 84 | - i int 85 | exclusions: 86 | generated: lax 87 | presets: 88 | - comments 89 | - common-false-positives 90 | - legacy 91 | - std-error-handling 92 | rules: 93 | - linters: 94 | - containedctx 95 | - err113 96 | - forcetypeassert 97 | - goconst 98 | - varnamelen 99 | - wrapcheck 100 | path: _test\.go 101 | paths: 102 | - third_party$ 103 | - builtin$ 104 | - examples$ 105 | issues: 106 | uniq-by-line: true 107 | new-from-rev: b5fdfaa2bd30e666511e4648f27d8a26fd8512cb 108 | new: false 109 | fix: false 110 | formatters: 111 | enable: 112 | - gofmt 113 | exclusions: 114 | generated: lax 115 | paths: 116 | - third_party$ 117 | - builtin$ 118 | - examples$ 119 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version v0.3.0 (2024-11-27) 4 | 5 | ### Features 6 | 7 | - **dingo:** introduce injection tracing (#68) (21c94ffa) 8 | 9 | ### Chores and tidying 10 | 11 | - **deps:** update module github.com/stretchr/testify to v1.10.0 (#69) (ef77a754) 12 | - update to go 1.22 and adjust linter rules (#67) (5f499b2e) 13 | - **deps:** update actions/checkout action to v4 (#60) (8e15da8c) 14 | - **deps:** update actions/setup-go action to v5 (#61) (d87ff516) 15 | - **deps:** update golangci/golangci-lint-action action to v6 (#66) (bc363bf9) 16 | - **deps:** update golangci/golangci-lint-action action to v5 (#65) (99893da6) 17 | - **deps:** update module github.com/stretchr/testify to v1.9.0 (#63) (a327329b) 18 | - update to go 1.21 minimum (#64) (e7b3183d) 19 | - **deps:** update module github.com/stretchr/testify to v1.8.4 (#59) (8b5d6f5e) 20 | - add regex to detect go run/install commands (88d11623) 21 | - **deps:** update module github.com/stretchr/testify to v1.8.2 (#55) (34f5abd5) 22 | - **deps:** update actions/setup-go action to v4 (#56) (c617aa9b) 23 | 24 | ## Version v0.2.10 (2022-11-04) 25 | 26 | ### Fixes 27 | 28 | - **deps:** update module github.com/stretchr/testify to v1.7.1 (75d21325) 29 | 30 | ### Ops and CI/CD 31 | 32 | - adjust gloangci-lint config for github actions (72b6052c) 33 | - make "new-from-rev" work for golangci-lint (76e21ab6) 34 | - remove unnecessary static checks (now handled by golangci-lint) (26838281) 35 | - introduce golangci-lint (213edd33) 36 | - **github:** update CI pipeline (f8a46198) 37 | - **semanticore:** change default branch (263538dc) 38 | - **semanticore:** add semanticore (10133afa) 39 | - switch to GitHub Actions, bump go versions and deps (#24) (6df0d341) 40 | 41 | ### Chores and tidying 42 | 43 | - **deps:** update irongut/codecoveragesummary action to v1.3.0 (#46) (7aae2361) 44 | - **deps:** update module github.com/stretchr/testify to v1.8.1 (#49) (00c4e9e1) 45 | - bump to go1.18 (a3c98a77) 46 | - bump to go1.18 (aae76969) 47 | - **deps:** update module github.com/stretchr/testify to v1.8.0 (#44) (b5fdfaa2) 48 | - **renovate:** use chore commit message (c4ee6660) 49 | - **deps:** update actions/setup-go action to v3 (47d41e94) 50 | - **deps:** update actions/checkout action to v3 (4a2de875) 51 | - **deps:** add renovate.json (6a6974a3) 52 | - update readme (b6cd3f74) 53 | 54 | ### Other 55 | 56 | - Add Testcase and handle pointer to interfaces in a guard clause (339d5584) 57 | - [#7]: failing on usage of struct receiver in 'Inject' method (bea6ce02) 58 | - remove unnecessary continue in dingo.go (a85f8336) 59 | - require go 1.13 (76908a07) 60 | 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 AOE GmbH 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Dingo 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/flamingo.me/dingo)](https://goreportcard.com/report/flamingo.me/dingo) [![GoDoc](https://godoc.org/flamingo.me/dingo?status.svg)](https://godoc.org/flamingo.me/dingo) [![Tests](https://github.com/i-love-flamingo/dingo/workflows/Tests/badge.svg?branch=master)](https://github.com/i-love-flamingo/dingo/actions?query=branch%3Amaster+workflow%3ATests) 4 | 5 | Dependency injection for go 6 | 7 | ## Hello Dingo 8 | 9 | Dingo works very similar to [Guice](https://github.com/google/guice/wiki/GettingStarted) 10 | 11 | Basically one binds implementations/factories to interfaces, which are then resolved by Dingo. 12 | 13 | Given that Dingo's idea is based on Guice we use similar examples in this documentation: 14 | 15 | The following example shows a BillingService with two injected dependencies. Please note 16 | that Go's nature does not allow constructors, and does not allow decorations/annotations 17 | beside struct-tags, thus, we only use struct tags (and later arguments for providers). 18 | 19 | Also, Go does not have a way to reference types (like Java's `Something.class`) we use either pointers 20 | or `nil` and cast it to a pointer to the interface we want to specify: `(*Something)(nil)`. 21 | Dingo then knows how to dereference it properly and derive the correct type `Something`. 22 | This is not necessary for structs, where we can just use the null value via `Something{}`. 23 | 24 | See the example folder for a complete example. 25 | 26 | ```go 27 | package example 28 | 29 | type BillingService struct { 30 | processor CreditCardProcessor 31 | transactionLog TransactionLog 32 | } 33 | 34 | func (billingservice *BillingService) Inject(processor CreditCardProcessor, transactionLog TransactionLog) { 35 | billingservice.processor = processor 36 | billingservice.transactionLog = transactionLog 37 | } 38 | 39 | func (billingservice *BillingService) ChargeOrder(order PizzaOrder, creditCard CreditCard) Receipt { 40 | // ... 41 | } 42 | ``` 43 | 44 | We want the BillingService to get certain dependencies, and configure this in a `BillingModule` 45 | which implements `dingo.Module`: 46 | 47 | ```go 48 | package example 49 | 50 | type BillingModule struct {} 51 | 52 | func (module *BillingModule) Configure(injector *dingo.Injector) { 53 | // This tells Dingo that whenever it sees a dependency on a TransactionLog, 54 | // it should satisfy the dependency using a DatabaseTransactionLog. 55 | injector.Bind(new(TransactionLog)).To(DatabaseTransactionLog{}) 56 | 57 | // Similarly, this binding tells Dingo that when CreditCardProcessor is used in 58 | // a dependency, that should be satisfied with a PaypalCreditCardProcessor. 59 | injector.Bind(new(CreditCardProcessor)).To(PaypalCreditCardProcessor{}) 60 | } 61 | ``` 62 | 63 | ## Requesting injection 64 | 65 | Every instance that is created through the container can use injection. 66 | 67 | Dingo supports two ways of requesting dependencies that should be injected: 68 | 69 | * usage of struct tags to allow structs to request injection into fields. This should be used for public fields. 70 | * implement a public Inject(...) method to request injections of private fields. Dingo calls this method automatically and passes the requested injections. 71 | 72 | For every requested injection (unless an exception applies) Dingo does the following: 73 | 74 | - Is there a binding? If so: delegate to the binding 75 | - Is the binding in a certain scope (Singleton)? If so, delegate to scope (might result in a new loop) 76 | - Binding is bound to an instance: inject instance 77 | - Binding is bound to a provider: call provider 78 | - Binding is bound to a type: request injection of this type (might return in a new loop to resolve the binding) 79 | - No binding? Try to create (only possible for concrete types, not interfaces or functions) 80 | 81 | 82 | *Example:* 83 | Here is another example using the Inject method for private fields 84 | ```go 85 | package example 86 | 87 | type MyBillingService struct { 88 | processor CreditCardProcessor 89 | accountId string 90 | } 91 | 92 | func (m *MyBillingService) Inject( 93 | processor CreditCardProcessor, 94 | config *struct { 95 | AccountId string `inject:"config:myModule.myBillingService.accountId"` 96 | }, 97 | ) { 98 | m.processor = CreditCardProcessor 99 | m.accountId = config.AccountId 100 | } 101 | ``` 102 | 103 | ### Usage of Providers 104 | 105 | Dingo allows to request the injection of provider instead of instances. 106 | A "Provider" for dingo is a function that return a new Instance of a certain type. 107 | 108 | ```go 109 | package example 110 | 111 | type pizzaProvider func() Pizza 112 | 113 | func (s *Service) Inject(provider pizzaProvider) { 114 | s.provider = provider 115 | } 116 | ``` 117 | 118 | If there is no concrete binding to the type `func() Pizza`, then instead of constructing one `Pizza` instance 119 | Dingo will create a new function which, on every call, will return a new instance of `Pizza`. 120 | 121 | The type must be of `func() T`, a function without any arguments which returns a type, which again has a binding. 122 | 123 | This allows to lazily create new objects whenever needed, instead of requesting the Dingo injector itself. 124 | 125 | 126 | You can use Providers and call them to always get a new instance. 127 | Dingo will provide you with an automatic implementation of a Provider if you did not bind a specific one. 128 | 129 | *Use a Provider instead of requesting the Type directly when*: 130 | 131 | * for lazy binding 132 | * if you need new instances on demand 133 | * In general, it is best practice using a Provider for everything that has a state that might be changed. This way you will avoid undesired side effects. That is especially important for dependencies in objects that are shared between requests - for example a controller! 134 | 135 | *Example 1:* 136 | This is the only code required to request a Provider as a dependency: 137 | ```go 138 | MyStructProvider func() *MyStruct 139 | MyStruct struct {} 140 | 141 | MyService struct { 142 | MyStructProvider MyStructProvider `inject:""` 143 | } 144 | 145 | ``` 146 | 147 | *Example 2:* 148 | ```go 149 | package example 150 | 151 | func createSomething(thing SomethingElse) Something{ 152 | return &MySomething{somethingElse: thing} 153 | } 154 | 155 | injector.Bind(new(Something)).ToProvider(createSomething) 156 | 157 | type somethingProvider func() Something 158 | 159 | type service struct { 160 | provider somethingProvider 161 | } 162 | ``` 163 | 164 | will essentially call `createSomething(new(SomethingElse))` everytime `SomethingProvider()` is called, 165 | passing the resulting instance through the injection to finalize uninjected fields. 166 | 167 | 168 | ### Optional injection 169 | 170 | An injection struct tag can be marked as optional by adding the suffix `,optional` to it. 171 | This means that for interfaces, slices, pointers etc where dingo can not resolve a concrete type, the `nil`-type is injected. 172 | 173 | You can check via `if my.Prop == nil` if this is nil. 174 | 175 | ## Bindings 176 | 177 | Dingo uses bindings to express dependencies resolutions, and will panic if there is more than one 178 | binding for a type with the same name (or unnamed), unless you use multibindings. 179 | 180 | ### Bind 181 | 182 | Bind creates a new binding, and tells Dingo how to resolve the type when it encounters a request for this type. 183 | Bindings can chain, but need to implement the correct interfaces. 184 | 185 | ```go 186 | injector.Bind(new(Something)) 187 | ``` 188 | 189 | ### AnnotatedWith 190 | 191 | By default a binding is unnamed, and thus requested with the `inject:""` tag. 192 | 193 | However, you can name bindings to have more concrete kinds of it. Using `AnnotatedWith` you can specify the name: 194 | 195 | ```go 196 | injector.Bind((*Something)(nil)).AnnotatedWith("myAnnotation") 197 | ``` 198 | 199 | It is requested via the `inject:"myAnnotation"` tag. For example: 200 | 201 | ```go 202 | struct { 203 | PaypalPaymentProcessor PaymentProcessor `inject:"Paypal"` 204 | } 205 | ``` 206 | 207 | ### To 208 | 209 | To defines which type should be created when this type is requested. 210 | This can be an Interface which implements to one it is bound to, or a concrete type. 211 | The type is then created via `reflect.New`. 212 | 213 | ```go 214 | injector.Bind(new(Something)).To(MyType{}) 215 | ``` 216 | 217 | ### ToProvider 218 | 219 | If you want a factory to create your types then you rather use `ToProvider` instead of `To`. 220 | 221 | `ToProvider` is a function which returns an instance (which again will go through Dingo to fill dependencies). 222 | 223 | Also, the provider can request arguments from Dingo which are necessary to construct the bounded type. 224 | If you need named arguments (e.g. a string instance annotated with a configuration value) you need to request 225 | an instance of an object with these annotations, because Go does not allow to pass any meta-information on function 226 | arguments. 227 | 228 | ```go 229 | func MyTypeProvider(se SomethingElse) *MyType { 230 | return &MyType{ 231 | Special: se.DoSomething(), 232 | } 233 | } 234 | 235 | injector.Bind(new(Something)).ToProvider(MyTypeProvider) 236 | ``` 237 | 238 | This example will make Dingo call `MyTypeProvider` and pass in an instance of `SomethingElse` as it's first argument, 239 | then take the result of `*MyType` as the value for `Something`. 240 | 241 | `ToProvider` takes precedence over `To`. 242 | 243 | ### ToInstance 244 | 245 | For situations where you have one, and only one, concrete instance you can use `ToInstance` to bind 246 | something to the concrete instance. This is not the same as a Singleton! 247 | (Even though the resulting behaviour is very similar.) 248 | 249 | ```go 250 | var myInstance = new(MyType) 251 | myInstance.Connect(somewhere) 252 | injector.Bind(new(Something)).ToInstance(myInstance) 253 | ``` 254 | 255 | You can also bind an instance it to a struct obviously, not only to interfaces. 256 | 257 | `ToInstance` takes precedence over both `To` and `ToProvider`. 258 | 259 | ### In (Singleton scopes) 260 | 261 | If really necessary it is possible to use singletons 262 | ``` 263 | .AsEagerSingleton() binds as a singleton, and loads it when the application is initialized 264 | .In(dingo.Singleton) makes it a global singleton 265 | .In(dingo.ChildSingleton) makes it a singleton limited to the current injector 266 | ``` 267 | 268 | `In` allows us to bind in a scope, making the created instances scoped in a certain way. 269 | 270 | Currently, Dingo only allows to bind to `dingo.Singleton` and `dingo.ChildSingleton`. 271 | 272 | ```go 273 | injector.Bind(new(Something)).In(dingo.Singleton).To(MyType{}) 274 | ``` 275 | 276 | #### dingo.Singleton 277 | 278 | The `dingo.Singleton` scope makes sure a dependency is only resolved once, and the result is 279 | reused. Because the Singleton needs synchronisation for types over multiple concurrent 280 | goroutines and make sure that a Singleton is only created once, the initial creation 281 | can be costly and also the injection of a Singleton is always taking more resources than creation 282 | of an immutable new object. 283 | 284 | The synchronisation is done on multiple levels, a first test tries to find the singleton, 285 | if that is not possible a lock-mechanism via a scoped Mutex takes care of delegating 286 | the concrete creation to one goroutine via a scope+type specific Mutex which then generates 287 | the Singleton and makes it available to other currently waiting injection requests, as 288 | well as future injection requests. 289 | 290 | By default, it is advised to not use Singletons whenever possible, and rather use 291 | immutable objects you inject whenever you need them. 292 | 293 | #### dingo.ChildSingleton 294 | 295 | The ChildSingleton is just another Singleton (actually of the same type), but dingo will create a new one 296 | for every derived child injector. 297 | 298 | This allows frameworks like Flamingo to distinguish at a root level between singleton scopes, e.g. for 299 | multi-page setups where we need a wide scope for routers. 300 | 301 | Since ChildSingleton is very similar to Singleton you should only use it with care. 302 | 303 | #### AsEagerSingleton 304 | 305 | Singleton creation is always costly due to synchronisation overhead, therefore 306 | Dingo bindings allow to mark a binding `AsEagerSingleton`. 307 | 308 | This makes sure the Singleton is created as soon as possible, before the rest of the Application 309 | runs. `AsEagerSingleton` implies `In(dingo.Singleton)`. 310 | 311 | ```go 312 | injector.Bind(new(Something)).To(MyType{}).AsEagerSingleton() 313 | ``` 314 | 315 | It is also possible to bind a concrete type without `To`: 316 | 317 | ```go 318 | injector.Bind(MyType{}).AsEagerSingleton() 319 | ``` 320 | 321 | Binding this type as an eager singleton inject the singleton instance whenever `MyType` is requested. `MyType` is a concrete type (struct) here, so we can use this mechanism to create an instance explicitly before the application is run. 322 | 323 | ### Override 324 | 325 | In rare cases you might have to override an existing binding, which can be done with `Override`: 326 | 327 | ```go 328 | injector.Override(new(Something), "").To(MyBetterType{}) 329 | ``` 330 | 331 | `Override` also returns a binding such as `Bind`, but removes the original binding. 332 | 333 | The second argument sets the annotation if you want to override a named binding. 334 | 335 | ### MultiBindings 336 | 337 | MultiBindings provide a way of binding multiple implementations of a type to a type, 338 | making the injection a list. 339 | 340 | Essentially this means that multiple modules are able to register for a type, and a user of this 341 | type can request an injection of a slice `[]T` to get a list of all registered bindings. 342 | 343 | ```go 344 | injector.BindMulti(new(Something)).To(MyType1{}) 345 | injector.BindMulti(new(Something)).To(MyType2{}) 346 | 347 | struct { 348 | List []Something `inject:""` // List is a slice of []Something{MyType1{}, MyType2{}} 349 | } 350 | ``` 351 | 352 | MultiBindings are used to allow multiple modules to register for a certain type, such as a list of 353 | encoders, subscribers, etc. 354 | 355 | Please note that MultiBindings are not always a clear pattern, as it might hide certain complexity. 356 | 357 | Usually it is easier to request some kind of registry in your module, and then register explicitly. 358 | 359 | 360 | ### Bind maps 361 | 362 | Similar to Multibindings, but with a key instead of a list 363 | ```go 364 | MyService struct { 365 | Ifaces map[string]Iface `inject:""` 366 | } 367 | 368 | injector.BindMap(new(Iface), "impl1").To(IfaceImpl{}) 369 | injector.BindMap(new(Iface), "impl2").To(IfaceImpl2{}) 370 | ``` 371 | 372 | ### Binding basic types 373 | 374 | Dingo allows binding values to `int`, `string` etc., such as with any other type. 375 | 376 | This can be used to inject configuration values. 377 | 378 | Flamingo makes an annotated binding of every configuration value in the form of: 379 | 380 | ```go 381 | var Configuration map[string]interface{} 382 | 383 | for k, v := range Configuration { 384 | injector.Bind(v).AnnotatedWith("config:" + k).ToInstance(v) 385 | } 386 | ``` 387 | 388 | In this case Dingo learns the actual type of `v` (such as string, bool, int) and provides the annotated injection. 389 | 390 | Later this can be used via 391 | 392 | ```go 393 | struct { 394 | ConfigParam string `inject:"config:myconfigParam"` 395 | } 396 | ``` 397 | 398 | 399 | 400 | ## Dingo Interception 401 | 402 | Dingo allows modules to bind interceptors for interfaces. 403 | 404 | Essentially this means that whenever the injection of a certain type is happening, 405 | the interceptor is injected instead with the actual injection injected into the interceptor's 406 | first field. This mechanism can only work for interface interception. 407 | 408 | Multiple interceptors stack upon each other. 409 | 410 | Interception should be used with care! 411 | 412 | ```go 413 | func (m *Module) Configure(injector *dingo.Injector) { 414 | injector.BindInterceptor(new(template.Engine), TplInterceptor{}) 415 | injector.BindInterceptor(new(template.Function), FunctionInterceptor{}) 416 | } 417 | 418 | type ( 419 | TplInterceptor struct { 420 | template.Engine 421 | } 422 | 423 | FunctionInterceptor struct { 424 | template.Function 425 | } 426 | ) 427 | 428 | func (t *TplInterceptor) Render(context web.Context, name string, data interface{}) io.Reader { 429 | slog.Info("Before Rendering", name) 430 | start := time.Now() 431 | r := t.Engine.Render(context, name, data) 432 | slog.Info("After Rendering", time.Since(start)) 433 | return r 434 | } 435 | 436 | func (f *FunctionInterceptor) Name() string { 437 | funcname := f.Function.Name() 438 | slog.Info("Function", funcname, "used") 439 | return funcname 440 | } 441 | ``` 442 | 443 | ## Initializing Dingo 444 | At the topmost level the injector is created and used in the following way: 445 | 446 | ```go 447 | package main 448 | 449 | import "flamingo.me/dingo" 450 | 451 | func main() { 452 | injector, err := dingo.NewInjector() 453 | if err != nil { 454 | panic(err) 455 | } 456 | 457 | // The injector can be initialized by modules: 458 | injector.InitModules(new(BillingModule)) 459 | 460 | // Now that we've got the injector, we can build objects. 461 | // We get a new instance, and cast it accordingly: 462 | instance, err := injector.GetInstance(new(BillingService)) 463 | if err != nil { 464 | panic(err) 465 | } 466 | billingService := instance.(BillingService) 467 | //... 468 | } 469 | ``` 470 | 471 | ## Dingo vs. Wire 472 | 473 | Recently https://github.com/google/go-cloud/tree/master/wire popped out in the go ecosystem, which seems to be a great choice, also because it supports compile time dependency injection. 474 | However, when Dingo was first introduced wire was not a thing, and wire still lacks features dingo provides. 475 | 476 | https://gocover.io/github.com/i-love-flamingo/dingo 477 | 478 | ## ModuleFunc 479 | 480 | Dingo has a wrapper for `func(*Injector)` called `ModuleFunc`. It is possible to wrap a function with the `ModuleFunc` to become a `Module`. 481 | This is similar to the `http` Packages `HandlerFunc` mechanism and allows to save code and easier set up small projects. 482 | 483 | ## Troubleshooting 484 | 1. To trace possible circular injections Dingo has function `EnableCircularTracing()`, which also switches slog to DEBUG level. This makes execution very heavy in terms of memory, so should be used only for debug purposes. 485 | 2. To trace possible injection issues, like when Dingo tries to inject dependency into unexported field and fails, and user does not know where this happens, Dingo has `EnableInjectionTracing()`, which is also sets slog level to DEBUG. 486 | -------------------------------------------------------------------------------- /binding.go: -------------------------------------------------------------------------------- 1 | package dingo 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | type ( 9 | // Binding defines a type mapped to a more concrete type 10 | Binding struct { 11 | typeof reflect.Type 12 | 13 | to reflect.Type 14 | instance *Instance 15 | provider *Provider 16 | 17 | eager bool 18 | annotatedWith string 19 | scope Scope 20 | } 21 | 22 | // Instance holds quick-references to type and value 23 | Instance struct { 24 | itype reflect.Type 25 | ivalue reflect.Value 26 | } 27 | 28 | // Provider holds the provider function 29 | Provider struct { 30 | fnctype reflect.Type 31 | fnc reflect.Value 32 | binding *Binding 33 | } 34 | ) 35 | 36 | // To binds a concrete type to a binding 37 | func (b *Binding) To(what interface{}) *Binding { 38 | to := reflect.TypeOf(what) 39 | 40 | for to.Kind() == reflect.Ptr { 41 | to = to.Elem() 42 | } 43 | 44 | if !to.AssignableTo(b.typeof) && !reflect.PtrTo(to).AssignableTo(b.typeof) { 45 | panic(fmt.Sprintf("%s#%s not assignable to %s#%s", to.PkgPath(), to.Name(), b.typeof.PkgPath(), b.typeof.Name())) 46 | } 47 | 48 | b.to = to 49 | 50 | return b 51 | } 52 | 53 | // ToInstance binds an instance to a binding 54 | func (b *Binding) ToInstance(instance interface{}) *Binding { 55 | b.instance = &Instance{ 56 | itype: reflect.TypeOf(instance), 57 | ivalue: reflect.ValueOf(instance), 58 | } 59 | if !b.instance.itype.AssignableTo(b.typeof) && !b.instance.itype.AssignableTo(reflect.PtrTo(b.typeof)) { 60 | panic(fmt.Sprintf("%s#%s not assignable to %s#%s", b.instance.itype.PkgPath(), b.instance.itype.Name(), b.typeof.PkgPath(), b.typeof.Name())) 61 | } 62 | return b 63 | } 64 | 65 | // ToProvider binds a provider to an instance. The provider's arguments are automatically injected 66 | func (b *Binding) ToProvider(p interface{}) *Binding { 67 | provider := &Provider{ 68 | fnc: reflect.ValueOf(p), 69 | binding: b, 70 | } 71 | provider.fnctype = provider.fnc.Type().Out(0) 72 | if !provider.fnctype.AssignableTo(b.typeof) && !provider.fnctype.AssignableTo(reflect.PtrTo(b.typeof)) { 73 | panic(fmt.Sprintf("provider returns %q which is not assignable to %q", provider.fnctype, b.typeof)) 74 | } 75 | b.provider = provider 76 | return b 77 | } 78 | 79 | // AnnotatedWith sets the binding's annotation 80 | func (b *Binding) AnnotatedWith(annotation string) *Binding { 81 | b.annotatedWith = annotation 82 | return b 83 | } 84 | 85 | // In set's the bindings scope 86 | func (b *Binding) In(scope Scope) *Binding { 87 | b.scope = scope 88 | return b 89 | } 90 | 91 | // AsEagerSingleton set's the binding to singleton and requests eager initialization 92 | func (b *Binding) AsEagerSingleton() *Binding { 93 | b.In(Singleton) 94 | b.eager = true 95 | return b 96 | } 97 | 98 | func (b *Binding) equal(to *Binding) bool { 99 | return reflect.DeepEqual(b, to) 100 | } 101 | 102 | // Create creates a new instance by the provider and requests injection, all provider arguments are automatically filled 103 | func (p *Provider) Create(injector *Injector) (reflect.Value, error) { 104 | in := make([]reflect.Value, p.fnc.Type().NumIn()) 105 | var err error 106 | for i := 0; i < p.fnc.Type().NumIn(); i++ { 107 | if in[i], err = injector.getInstance(p.fnc.Type().In(i), "", traceCircular); err != nil { 108 | return reflect.Value{}, err 109 | } 110 | for !in[i].Type().AssignableTo(p.fnc.Type().In(i)) && in[i].Kind() == reflect.Ptr { 111 | in[i] = in[i].Elem() 112 | } 113 | } 114 | res := p.fnc.Call(in)[0] 115 | return res, injector.requestInjection(res, traceCircular) 116 | } 117 | -------------------------------------------------------------------------------- /binding_test.go: -------------------------------------------------------------------------------- 1 | package dingo 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestBinding_To(t *testing.T) { 11 | b := &Binding{typeof: reflect.TypeOf(new(string)).Elem()} 12 | 13 | b.To(new(string)) 14 | assert.Equal(t, b.to, b.typeof) 15 | 16 | assert.Panics(t, func() { 17 | b.To(new(int)) 18 | }) 19 | } 20 | 21 | func TestBinding_ToInstance(t *testing.T) { 22 | b := &Binding{typeof: reflect.TypeOf(new(string)).Elem()} 23 | 24 | b.ToInstance("test") 25 | assert.Equal(t, b.instance.itype, b.typeof) 26 | 27 | assert.Panics(t, func() { 28 | b.ToInstance(123) 29 | }) 30 | } 31 | 32 | func TestBinding_ToProvider(t *testing.T) { 33 | b := &Binding{typeof: reflect.TypeOf(new(string)).Elem()} 34 | 35 | b.ToProvider(func() string { return "test" }) 36 | assert.Equal(t, b.provider.fnctype, b.typeof) 37 | 38 | assert.Panics(t, func() { 39 | b.ToProvider(b.ToProvider(func() int { return 123 })) 40 | }) 41 | } 42 | 43 | func TestBinding_equal(t *testing.T) { 44 | b := &Binding{typeof: reflect.TypeOf(new(string)).Elem()} 45 | b2 := &Binding{typeof: reflect.TypeOf(new(string)).Elem()} 46 | b3 := &Binding{typeof: reflect.TypeOf(new(int)).Elem()} 47 | 48 | assert.True(t, b.equal(b2)) 49 | assert.False(t, b.equal(b3)) 50 | } 51 | -------------------------------------------------------------------------------- /circular_test.go: -------------------------------------------------------------------------------- 1 | package dingo 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | type ( 10 | circA struct { 11 | A *circA `inject:""` 12 | B *circB `inject:""` 13 | } 14 | 15 | circB struct { 16 | A *circA `inject:""` 17 | B *circB `inject:""` 18 | } 19 | 20 | circCProvider func() circCInterface 21 | circCInterface interface{} 22 | circC struct { 23 | C circCProvider `inject:""` 24 | } 25 | 26 | circAProvider func() *circA 27 | circD struct { 28 | A circAProvider `inject:""` 29 | } 30 | ) 31 | 32 | func TestDingoCircula(t *testing.T) { 33 | EnableCircularTracing() 34 | defer func() { 35 | traceCircular = nil 36 | }() 37 | 38 | injector, err := NewInjector() 39 | assert.NoError(t, err) 40 | 41 | assert.Panics(t, func() { 42 | i, err := injector.GetInstance(new(circA)) 43 | assert.NoError(t, err) 44 | _, ok := i.(*circA) 45 | if !ok { 46 | t.Fail() 47 | } 48 | }) 49 | 50 | injector.Bind(new(circCInterface)).To(circC{}) 51 | 52 | i, err := injector.GetInstance(new(circC)) 53 | assert.NoError(t, err) 54 | c, ok := i.(*circC) 55 | if !ok { 56 | t.Fail() 57 | } 58 | assert.NotNil(t, c.C()) 59 | 60 | var d *circD 61 | assert.NotPanics(t, func() { 62 | var ok bool 63 | i, err := injector.GetInstance(new(circD)) 64 | assert.NoError(t, err) 65 | d, ok = i.(*circD) 66 | if !ok { 67 | t.Fail() 68 | } 69 | }) 70 | 71 | assert.Panics(t, func() { 72 | d.A() 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /dingo.go: -------------------------------------------------------------------------------- 1 | package dingo 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log/slog" 7 | "reflect" 8 | "strings" 9 | ) 10 | 11 | const ( 12 | // INIT state 13 | INIT = iota 14 | // DEFAULT state 15 | DEFAULT 16 | ) 17 | 18 | var ( 19 | ErrInvalidInjectReceiver = errors.New("usage of 'Inject' method with struct receiver is not allowed") 20 | errPointersToInterface = errors.New(" Do not use pointers to interface.") 21 | 22 | traceCircular []circularTraceEntry 23 | injectionTracing = false 24 | ) 25 | 26 | // EnableCircularTracing activates dingo's trace feature to find circular dependencies 27 | // this is super expensive (memory wise), so it should only be used for debugging purposes 28 | func EnableCircularTracing() { 29 | traceCircular = make([]circularTraceEntry, 0) 30 | } 31 | 32 | func EnableInjectionTracing() { 33 | injectionTracing = true 34 | } 35 | 36 | type ( 37 | // Injector defines bindings and multibindings 38 | // it is possible to have a parent-injector, which can be asked if no resolution is available 39 | Injector struct { 40 | bindings map[reflect.Type][]*Binding // list of available bindings for a concrete type 41 | multibindings map[reflect.Type][]*Binding // list of multi-bindings for a concrete type 42 | mapbindings map[reflect.Type]map[string]*Binding // list of map-bindings for a concrete type 43 | interceptor map[reflect.Type][]reflect.Type // list of interceptors for a type 44 | overrides []*override // list of overrides for a binding 45 | parent *Injector // parent injector reference 46 | scopes map[reflect.Type]Scope // scope-bindings 47 | stage uint // current stage 48 | delayed []interface{} // delayed bindings 49 | buildEagerSingletons bool // weather to build singletons 50 | } 51 | 52 | // overrides are evaluated lazy, so they are scheduled here 53 | override struct { 54 | typ reflect.Type 55 | annotatedWith string 56 | binding *Binding 57 | } 58 | 59 | circularTraceEntry struct { 60 | typ reflect.Type 61 | annotation string 62 | } 63 | ) 64 | 65 | // NewInjector builds up a new Injector out of a list of Modules 66 | func NewInjector(modules ...Module) (*Injector, error) { 67 | injector := &Injector{ 68 | bindings: make(map[reflect.Type][]*Binding), 69 | multibindings: make(map[reflect.Type][]*Binding), 70 | mapbindings: make(map[reflect.Type]map[string]*Binding), 71 | interceptor: make(map[reflect.Type][]reflect.Type), 72 | scopes: make(map[reflect.Type]Scope), 73 | stage: DEFAULT, 74 | buildEagerSingletons: true, 75 | } 76 | 77 | // bind current injector 78 | injector.Bind(Injector{}).ToInstance(injector) 79 | 80 | // bind default scopes 81 | injector.BindScope(Singleton) 82 | injector.BindScope(ChildSingleton) 83 | 84 | // init current modules 85 | return injector, injector.InitModules(modules...) 86 | } 87 | 88 | // Child derives a child injector with a new ChildSingletonScope 89 | func (injector *Injector) Child() (*Injector, error) { 90 | if injector == nil { 91 | return nil, errors.New("can not create a child of an uninitialized injector") 92 | } 93 | 94 | newInjector, err := NewInjector() 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | newInjector.parent = injector 100 | newInjector.Bind(Injector{}).ToInstance(newInjector) 101 | newInjector.BindScope(NewChildSingletonScope()) // bind a new child-singleton 102 | 103 | return newInjector, nil 104 | } 105 | 106 | // InitModules initializes the injector with the given modules 107 | func (injector *Injector) InitModules(modules ...Module) error { 108 | injector.stage = INIT 109 | 110 | modules = resolveDependencies(modules, nil) 111 | for _, module := range modules { 112 | if err := injector.requestInjection(module, traceCircular); err != nil { 113 | erroredModule := reflect.TypeOf(module).Elem() 114 | return fmt.Errorf("initmodules: injection into %q failed: %w", erroredModule.PkgPath()+"."+erroredModule.Name(), err) 115 | } 116 | module.Configure(injector) 117 | } 118 | 119 | // evaluate overrides when modules were loaded 120 | for _, override := range injector.overrides { 121 | bindtype := override.typ 122 | if bindtype.Kind() == reflect.Ptr { 123 | bindtype = bindtype.Elem() 124 | } 125 | if bindings, ok := injector.bindings[bindtype]; ok && len(bindings) > 0 { 126 | for i, binding := range bindings { 127 | if binding.annotatedWith == override.annotatedWith { 128 | injector.bindings[bindtype][i] = override.binding 129 | } 130 | } 131 | continue 132 | } 133 | return fmt.Errorf("cannot override unknown binding %q (annotated with %q)", override.typ.String(), override.annotatedWith) // todo ok? 134 | } 135 | 136 | // make sure there are no duplicated bindings 137 | for typ, bindings := range injector.bindings { 138 | known := make(map[string]*Binding) 139 | for _, binding := range bindings { 140 | if known, ok := known[binding.annotatedWith]; ok && !known.equal(binding) { 141 | var knownBinding, duplicateBinding string 142 | if known.to != nil { 143 | knownBinding = fmt.Sprintf("%#v%#v", known.to.PkgPath(), known.to.Name()) 144 | } 145 | if binding.to != nil { 146 | duplicateBinding = fmt.Sprintf("%#v%#v", binding.to.PkgPath(), binding.to.Name()) 147 | } 148 | return fmt.Errorf("already known binding for %q with annotation %q | Known binding: %q Try %q", typ, binding.annotatedWith, knownBinding, duplicateBinding) 149 | } 150 | known[binding.annotatedWith] = binding 151 | } 152 | } 153 | 154 | injector.stage = DEFAULT 155 | 156 | // continue with delayed injections 157 | for _, object := range injector.delayed { 158 | if err := injector.requestInjection(object, traceCircular); err != nil { 159 | return err 160 | } 161 | } 162 | 163 | injector.delayed = nil 164 | 165 | // build eager singletons 166 | if !injector.buildEagerSingletons { 167 | return nil 168 | } 169 | return injector.BuildEagerSingletons(false) 170 | } 171 | 172 | // SetBuildEagerSingletons can be used to disable or enable building of eager singletons during InitModules 173 | func (injector *Injector) SetBuildEagerSingletons(build bool) { 174 | injector.buildEagerSingletons = build 175 | } 176 | 177 | // BuildEagerSingletons requests one instance of each singleton, optional letting the parent injector(s) do the same 178 | func (injector *Injector) BuildEagerSingletons(includeParent bool) error { 179 | for _, bindings := range injector.bindings { 180 | for _, binding := range bindings { 181 | if binding.eager { 182 | if _, err := injector.getInstance(binding.typeof, binding.annotatedWith, traceCircular); err != nil { 183 | return fmt.Errorf("initmodules: loading eager singletons: %w", err) 184 | } 185 | } 186 | } 187 | } 188 | if includeParent && injector.parent != nil { 189 | return injector.parent.BuildEagerSingletons(includeParent) 190 | } 191 | return nil 192 | } 193 | 194 | // GetInstance creates a new instance of what was requested 195 | func (injector *Injector) GetInstance(of interface{}) (interface{}, error) { 196 | i, err := injector.getInstance(of, "", traceCircular) 197 | if err != nil { 198 | return nil, err 199 | } 200 | return i.Interface(), nil 201 | } 202 | 203 | // GetAnnotatedInstance creates a new instance of what was requested with the given annotation 204 | func (injector *Injector) GetAnnotatedInstance(of interface{}, annotatedWith string) (interface{}, error) { 205 | i, err := injector.getInstance(of, annotatedWith, traceCircular) 206 | if err != nil { 207 | return nil, err 208 | } 209 | return i.Interface(), nil 210 | } 211 | 212 | // getInstance creates the new instance of typ, returns a reflect.value 213 | func (injector *Injector) getInstance(typ interface{}, annotatedWith string, circularTrace []circularTraceEntry) (reflect.Value, error) { 214 | oftype := reflect.TypeOf(typ) 215 | 216 | if oft, ok := typ.(reflect.Type); ok { 217 | oftype = oft 218 | } else { 219 | for oftype.Kind() == reflect.Ptr { 220 | oftype = oftype.Elem() 221 | } 222 | } 223 | 224 | return injector.getInstanceOfTypeWithAnnotation(oftype, annotatedWith, nil, false, circularTrace) 225 | } 226 | 227 | func (injector *Injector) findBindingForAnnotatedType(t reflect.Type, annotation string) *Binding { 228 | if len(injector.bindings[t]) > 0 { 229 | for _, binding := range injector.bindings[t] { 230 | if binding.annotatedWith == annotation { 231 | return binding 232 | } 233 | } 234 | } 235 | 236 | // inject one key of a map-binding 237 | if len(annotation) > 4 && annotation[:4] == "map:" { 238 | return injector.mapbindings[t][annotation[4:]] 239 | } 240 | 241 | // ask parent 242 | if injector.parent != nil { 243 | return injector.parent.findBindingForAnnotatedType(t, annotation) 244 | } 245 | 246 | return nil 247 | } 248 | 249 | // getInstanceOfTypeWithAnnotation resolves a requested type, with annotation 250 | func (injector *Injector) getInstanceOfTypeWithAnnotation(t reflect.Type, annotation string, binding *Binding, optional bool, circularTrace []circularTraceEntry) (reflect.Value, error) { 251 | if t.Kind() == reflect.Ptr { 252 | t = t.Elem() 253 | } 254 | 255 | var final reflect.Value 256 | var err error 257 | 258 | if typeBinding := injector.findBindingForAnnotatedType(t, annotation); typeBinding != nil { 259 | binding = typeBinding 260 | } 261 | if binding != nil { 262 | if binding.scope != nil { 263 | if scope, ok := injector.scopes[reflect.TypeOf(binding.scope)]; ok { 264 | if final, err = scope.ResolveType(t, annotation, func(t reflect.Type, annotation string, optional bool) (reflect.Value, error) { 265 | return injector.createInstanceOfAnnotatedType(t, annotation, optional, circularTrace) 266 | }); err != nil { 267 | return reflect.Value{}, err 268 | } 269 | if !final.IsValid() { 270 | return reflect.Value{}, fmt.Errorf("%T did not resolve %s", scope, t) 271 | } 272 | } else { 273 | return reflect.Value{}, fmt.Errorf("unknown scope %T for %s", binding.scope, t) 274 | } 275 | } 276 | } 277 | 278 | if !final.IsValid() { 279 | if final, err = injector.createInstanceOfAnnotatedType(t, annotation, optional, circularTrace); err != nil { 280 | return reflect.Value{}, err 281 | } 282 | } 283 | 284 | if !final.IsValid() { 285 | return reflect.Value{}, fmt.Errorf("can not resolve %q", t.String()) 286 | } 287 | 288 | return injector.intercept(final, t) 289 | } 290 | 291 | func (injector *Injector) intercept(final reflect.Value, t reflect.Type) (reflect.Value, error) { 292 | for _, interceptor := range injector.interceptor[t] { 293 | of := final 294 | final = reflect.New(interceptor) 295 | if err := injector.requestInjection(final.Interface(), traceCircular); err != nil { 296 | return reflect.Value{}, err 297 | } 298 | final.Elem().Field(0).Set(of) 299 | } 300 | if injector.parent != nil { 301 | return injector.parent.intercept(final, t) 302 | } 303 | return final, nil 304 | } 305 | 306 | type errUnbound struct { 307 | binding *Binding 308 | typ reflect.Type 309 | } 310 | 311 | func (err errUnbound) Error() string { 312 | return fmt.Sprintf("binding is not bound: %v for %s", err.binding, err.typ) 313 | } 314 | 315 | func (injector *Injector) resolveBinding(binding *Binding, t reflect.Type, optional bool, circularTrace []circularTraceEntry) (reflect.Value, error) { 316 | if binding.instance != nil { 317 | return binding.instance.ivalue, nil 318 | } 319 | 320 | if binding.provider != nil { 321 | return binding.provider.Create(injector) 322 | } 323 | 324 | if binding.to != nil { 325 | if binding.to == t { 326 | return reflect.Value{}, fmt.Errorf("circular from %q to %q (annotated with: %q)", t, binding.to, binding.annotatedWith) 327 | } 328 | return injector.getInstanceOfTypeWithAnnotation(binding.to, "", binding, optional, circularTrace) 329 | } 330 | 331 | return reflect.Value{}, errUnbound{binding: binding, typ: t} 332 | } 333 | 334 | // createInstanceOfAnnotatedType resolves a type request with the current injector 335 | func (injector *Injector) createInstanceOfAnnotatedType(t reflect.Type, annotation string, optional bool, circularTrace []circularTraceEntry) (reflect.Value, error) { 336 | if binding := injector.findBindingForAnnotatedType(t, annotation); binding != nil { 337 | r, err := injector.resolveBinding(binding, t, optional, circularTrace) 338 | if err == nil || !errors.As(err, new(errUnbound)) { 339 | return r, err 340 | } 341 | 342 | // todo: proper testcases 343 | if annotation != "" { 344 | return injector.getInstanceOfTypeWithAnnotation(binding.typeof, "", binding, false, circularTrace) 345 | } 346 | } 347 | 348 | // This for an injection request on a provider, such as `func() MyInstance` 349 | if t.Kind() == reflect.Func && (t.NumOut() == 1 || t.NumOut() == 2) && strings.HasSuffix(t.Name(), "Provider") { 350 | providerCanError := t.NumOut() == 2 && t.Out(1).AssignableTo(reflect.TypeOf(new(error)).Elem()) 351 | if traceCircular != nil { 352 | return injector.createProvider(t, annotation, optional, providerCanError, make([]circularTraceEntry, 0)), nil 353 | } 354 | return injector.createProvider(t, annotation, optional, providerCanError, nil), nil 355 | } 356 | 357 | // This is the injection request for multibindings 358 | if t.Kind() == reflect.Slice { 359 | return injector.resolveMultibinding(t, annotation, optional, circularTrace) 360 | } 361 | 362 | // Map Binding injection 363 | if t.Kind() == reflect.Map && t.Key().Kind() == reflect.String { 364 | return injector.resolveMapbinding(t, annotation, optional, circularTrace) 365 | } 366 | 367 | if annotation != "" && !optional { 368 | return reflect.Value{}, fmt.Errorf("can not automatically create an annotated injection %q with annotation %q", t, annotation) 369 | } 370 | 371 | if t.Kind() == reflect.Interface && !optional { 372 | return reflect.Value{}, fmt.Errorf("can not instantiate interface %s.%s", t.PkgPath(), t.Name()) 373 | } 374 | 375 | if t.Kind() == reflect.Func && !optional { 376 | return reflect.Value{}, fmt.Errorf("can not create a new function %q (Do you want a provider? Then suffix type with Provider)", t) 377 | } 378 | 379 | if circularTrace != nil { 380 | for _, ct := range circularTrace { 381 | if ct.typ == t && ct.annotation == annotation { 382 | for _, ct := range circularTrace { 383 | slog.Info(fmt.Sprintf("%s#%s: %s", ct.typ.PkgPath(), ct.typ.Name(), ct.annotation)) 384 | } 385 | 386 | slog.Info(fmt.Sprintf("%s#%s: %s", t.PkgPath(), t.Name(), annotation)) 387 | 388 | panic("detected circular dependency") 389 | } 390 | } 391 | subCircularTrace := make([]circularTraceEntry, len(circularTrace)) 392 | copy(subCircularTrace, circularTrace) 393 | subCircularTrace = append(subCircularTrace, circularTraceEntry{t, annotation}) 394 | 395 | n := reflect.New(t) 396 | return n, injector.requestInjection(n.Interface(), subCircularTrace) 397 | } 398 | 399 | if injectionTracing { 400 | if t.PkgPath() == "" || t.Name() == "" { 401 | slog.Info(fmt.Sprintf("INJECTING: %s", t.String())) 402 | } else { 403 | slog.Info(fmt.Sprintf("INJECTING: %s#%s \"%s\"", t.PkgPath(), t.Name(), annotation)) 404 | } 405 | } 406 | 407 | n := reflect.New(t) 408 | return n, injector.requestInjection(n.Interface(), nil) 409 | } 410 | 411 | func reflectedError(err *error, t reflect.Type) reflect.Value { 412 | rerr := reflect.New(reflect.TypeOf(new(error)).Elem()).Elem() 413 | if err == nil || *err == nil { 414 | return rerr 415 | } 416 | rerr.Set(reflect.ValueOf(fmt.Errorf("%q: %w", t, *err))) 417 | return rerr 418 | } 419 | 420 | func (injector *Injector) createProvider(t reflect.Type, annotation string, optional bool, canError bool, circularTrace []circularTraceEntry) reflect.Value { 421 | return reflect.MakeFunc(t, func(args []reflect.Value) (results []reflect.Value) { 422 | // create a new type 423 | res := reflect.New(t.Out(0)) 424 | // dereference possible interface pointer 425 | if res.Kind() == reflect.Ptr && (res.Elem().Kind() == reflect.Interface || res.Elem().Kind() == reflect.Ptr) { 426 | res = res.Elem() 427 | } 428 | 429 | ret := func(v reflect.Value, err error) []reflect.Value { 430 | if err != nil && !canError { 431 | panic(fmt.Errorf("%q: %w", t, err)) 432 | } else if canError { 433 | return []reflect.Value{v, reflectedError(&err, t)} 434 | } else { 435 | return []reflect.Value{v} 436 | } 437 | } 438 | 439 | // multibindings 440 | if res.Elem().Kind() == reflect.Slice { 441 | return ret(injector.createInstanceOfAnnotatedType(t.Out(0), annotation, optional, circularTrace)) 442 | } 443 | 444 | // mapbindings 445 | if res.Elem().Kind() == reflect.Map && res.Elem().Type().Key().Kind() == reflect.String { 446 | return ret(injector.createInstanceOfAnnotatedType(t.Out(0), annotation, optional, circularTrace)) 447 | } 448 | 449 | r := ret(injector.getInstance(t.Out(0), annotation, circularTrace)) 450 | 451 | res.Set(r[0]) 452 | r[0] = res 453 | 454 | return r 455 | }) 456 | } 457 | 458 | func (injector *Injector) createProviderForBinding(t reflect.Type, binding *Binding, annotation string, optional bool, canError bool, circularTrace []circularTraceEntry) reflect.Value { 459 | return reflect.MakeFunc(t, func(args []reflect.Value) (results []reflect.Value) { 460 | // create a new type 461 | res := reflect.New(binding.typeof) 462 | // dereference possible interface pointer 463 | if res.Kind() == reflect.Ptr && (res.Elem().Kind() == reflect.Interface || res.Elem().Kind() == reflect.Ptr) { 464 | res = res.Elem() 465 | } 466 | 467 | if r, err := injector.resolveBinding(binding, t, optional, circularTrace); err == nil { 468 | res.Set(r) 469 | if canError { 470 | return []reflect.Value{res, reflectedError(nil, t)} 471 | } 472 | return []reflect.Value{res} 473 | } 474 | 475 | // set to actual value 476 | i, err := injector.getInstance(binding.typeof, annotation, circularTrace) 477 | if err != nil { 478 | if canError { 479 | return []reflect.Value{res, reflectedError(&err, t)} 480 | } 481 | panic(fmt.Errorf("%q: %w", t, err)) 482 | } 483 | res.Set(i) 484 | // return 485 | if canError { 486 | return []reflect.Value{res, reflectedError(nil, t)} 487 | } 488 | return []reflect.Value{res} 489 | }) 490 | } 491 | 492 | func (injector *Injector) joinMultibindings(t reflect.Type, annotation string) []*Binding { 493 | var parent []*Binding 494 | if injector.parent != nil { 495 | parent = injector.parent.joinMultibindings(t, annotation) 496 | } 497 | 498 | bindings := make([]*Binding, len(parent)+len(injector.multibindings[t])) 499 | copy(bindings, parent) 500 | c := len(parent) 501 | for _, b := range injector.multibindings[t] { 502 | if b.annotatedWith == annotation { 503 | bindings[c] = b 504 | c++ 505 | } 506 | } 507 | return bindings[:c] 508 | } 509 | 510 | func (injector *Injector) resolveMultibinding(t reflect.Type, annotation string, optional bool, circularTrace []circularTraceEntry) (reflect.Value, error) { 511 | targetType := t.Elem() 512 | if targetType.Kind() == reflect.Ptr { 513 | targetType = targetType.Elem() 514 | } 515 | 516 | providerType := targetType 517 | provider := strings.HasSuffix(targetType.Name(), "Provider") && targetType.Kind() == reflect.Func 518 | providerCanError := provider && targetType.NumOut() == 2 && targetType.Out(1).AssignableTo(reflect.TypeOf(new(error)).Elem()) 519 | 520 | if provider { 521 | targetType = targetType.Out(0) 522 | } 523 | 524 | if bindings := injector.joinMultibindings(targetType, annotation); len(bindings) > 0 { 525 | n := reflect.MakeSlice(t, 0, len(bindings)) 526 | for _, binding := range bindings { 527 | if provider { 528 | n = reflect.Append(n, injector.createProviderForBinding(providerType, binding, annotation, false, providerCanError, circularTrace)) 529 | continue 530 | } 531 | 532 | r, err := injector.resolveBinding(binding, t, optional, circularTrace) 533 | if err != nil { 534 | return reflect.Value{}, err 535 | } 536 | n = reflect.Append(n, r) 537 | } 538 | return n, nil 539 | } 540 | 541 | return reflect.MakeSlice(t, 0, 0), nil 542 | } 543 | 544 | func (injector *Injector) joinMapbindings(t reflect.Type, annotation string) map[string]*Binding { 545 | var parent map[string]*Binding 546 | if injector.parent != nil { 547 | parent = injector.parent.joinMapbindings(t, annotation) 548 | } 549 | 550 | bindings := make(map[string]*Binding, len(parent)+len(injector.multibindings[t])) 551 | for k, v := range parent { 552 | bindings[k] = v 553 | } 554 | for k, v := range injector.mapbindings[t] { 555 | if v.annotatedWith == annotation { 556 | bindings[k] = v 557 | } 558 | } 559 | return bindings 560 | } 561 | 562 | func (injector *Injector) resolveMapbinding(t reflect.Type, annotation string, optional bool, circularTrace []circularTraceEntry) (reflect.Value, error) { 563 | targetType := t.Elem() 564 | if targetType.Kind() == reflect.Ptr { 565 | targetType = targetType.Elem() 566 | } 567 | 568 | providerType := targetType 569 | provider := strings.HasSuffix(targetType.Name(), "Provider") && targetType.Kind() == reflect.Func 570 | providerCanError := provider && targetType.NumOut() == 2 && targetType.Out(1).AssignableTo(reflect.TypeOf(new(error)).Elem()) 571 | 572 | if provider { 573 | targetType = targetType.Out(0) 574 | } 575 | 576 | if bindings := injector.joinMapbindings(targetType, annotation); len(bindings) > 0 { 577 | n := reflect.MakeMapWithSize(t, len(bindings)) 578 | for key, binding := range bindings { 579 | if provider { 580 | n.SetMapIndex(reflect.ValueOf(key), injector.createProviderForBinding(providerType, binding, annotation, false, providerCanError, circularTrace)) 581 | continue 582 | } 583 | 584 | r, err := injector.resolveBinding(binding, t, optional, circularTrace) 585 | if err != nil { 586 | return reflect.Value{}, err 587 | } 588 | n.SetMapIndex(reflect.ValueOf(key), r) 589 | } 590 | return n, nil 591 | } 592 | 593 | return reflect.MakeMap(t), nil 594 | } 595 | 596 | // BindMulti binds multiple concrete types to the same abstract type / interface 597 | func (injector *Injector) BindMulti(what interface{}) *Binding { 598 | bindtype := reflect.TypeOf(what) 599 | if bindtype.Kind() == reflect.Ptr { 600 | bindtype = bindtype.Elem() 601 | } 602 | binding := new(Binding) 603 | binding.typeof = bindtype 604 | imb := injector.multibindings[bindtype] 605 | imb = append(imb, binding) 606 | injector.multibindings[bindtype] = imb 607 | return binding 608 | } 609 | 610 | // BindMap does a registry-like map-based binding, like BindMulti 611 | func (injector *Injector) BindMap(what interface{}, key string) *Binding { 612 | bindtype := reflect.TypeOf(what) 613 | if bindtype.Kind() == reflect.Ptr { 614 | bindtype = bindtype.Elem() 615 | } 616 | binding := new(Binding) 617 | binding.typeof = bindtype 618 | bindingMap := injector.mapbindings[bindtype] 619 | if bindingMap == nil { 620 | bindingMap = make(map[string]*Binding) 621 | } 622 | bindingMap[key] = binding 623 | injector.mapbindings[bindtype] = bindingMap 624 | 625 | return binding 626 | } 627 | 628 | // BindInterceptor intercepts to interface with interceptor 629 | func (injector *Injector) BindInterceptor(to, interceptor interface{}) { 630 | totype := reflect.TypeOf(to) 631 | if totype.Kind() == reflect.Ptr { 632 | totype = totype.Elem() 633 | } 634 | if totype.Kind() != reflect.Interface { 635 | panic("can only intercept interfaces " + fmt.Sprintf("%v", to)) 636 | } 637 | m := injector.interceptor[totype] 638 | m = append(m, reflect.TypeOf(interceptor)) 639 | injector.interceptor[totype] = m 640 | } 641 | 642 | // BindScope binds a scope to be aware of 643 | func (injector *Injector) BindScope(s Scope) { 644 | injector.scopes[reflect.TypeOf(s)] = s 645 | } 646 | 647 | // Bind creates a new binding for an abstract type / interface 648 | // Use the syntax 649 | // 650 | // injector.Bind((*Interface)(nil)) 651 | // 652 | // To specify the interface (cast it to a pointer to a nil of the type Interface) 653 | func (injector *Injector) Bind(what interface{}) *Binding { 654 | if what == nil { 655 | panic("Cannot bind nil") 656 | } 657 | bindtype := reflect.TypeOf(what) 658 | if bindtype.Kind() == reflect.Ptr { 659 | bindtype = bindtype.Elem() 660 | } 661 | binding := new(Binding) 662 | binding.typeof = bindtype 663 | injector.bindings[bindtype] = append(injector.bindings[bindtype], binding) 664 | return binding 665 | } 666 | 667 | // Override a binding 668 | func (injector *Injector) Override(what interface{}, annotatedWith string) *Binding { 669 | binding := injector.Bind(what).AnnotatedWith(annotatedWith) 670 | injector.overrides = append(injector.overrides, &override{typ: binding.typeof, annotatedWith: annotatedWith, binding: binding}) 671 | return binding 672 | } 673 | 674 | // RequestInjection requests the object to have all fields annotated with `inject` to be filled 675 | func (injector *Injector) RequestInjection(object interface{}) error { 676 | if injector.stage == INIT { 677 | injector.delayed = append(injector.delayed, object) 678 | } else { 679 | return injector.requestInjection(object, traceCircular) 680 | } 681 | return nil 682 | } 683 | 684 | func (injector *Injector) requestInjection(object interface{}, circularTrace []circularTraceEntry) error { 685 | if _, ok := object.(reflect.Value); !ok { 686 | object = reflect.ValueOf(object) 687 | } 688 | var injectlist = []reflect.Value{object.(reflect.Value)} 689 | var i int 690 | var current reflect.Value 691 | var err error 692 | 693 | wrapErr := func(err error) error { 694 | path := current.Type().PkgPath() 695 | if path == "" { 696 | if current.Kind() == reflect.Ptr { 697 | path = current.Elem().Type().PkgPath() 698 | } 699 | } 700 | if path != "" { 701 | path += "." 702 | } 703 | return fmt.Errorf("injecting into %s%s:\n%w", path, current.String(), err) 704 | } 705 | 706 | for { 707 | if i >= len(injectlist) { 708 | break 709 | } 710 | 711 | current = injectlist[i] 712 | ctype := current.Type() 713 | 714 | i++ 715 | 716 | if ctype.Kind() != reflect.Ptr && current.MethodByName("Inject").IsValid() { 717 | return fmt.Errorf("invalid inject receiver %s: %w", current, ErrInvalidInjectReceiver) 718 | } 719 | 720 | switch ctype.Kind() { 721 | // dereference pointer 722 | case reflect.Ptr: 723 | if setup := current.MethodByName("Inject"); setup.IsValid() { 724 | args := make([]reflect.Value, setup.Type().NumIn()) 725 | for i := range args { 726 | if args[i], err = injector.getInstance(setup.Type().In(i), "", circularTrace); err != nil { 727 | return wrapErr(err) 728 | } 729 | } 730 | setup.Call(args) 731 | } 732 | injectlist = append(injectlist, current.Elem()) 733 | 734 | // inject into struct fields 735 | case reflect.Struct: 736 | for fieldIndex := 0; fieldIndex < ctype.NumField(); fieldIndex++ { 737 | if tag, ok := ctype.Field(fieldIndex).Tag.Lookup("inject"); ok { 738 | field := current.Field(fieldIndex) 739 | currentFieldName := ctype.Field(fieldIndex).Name 740 | if field.Kind() == reflect.Struct { 741 | return fmt.Errorf("can not inject into struct %#v of %#v", field, current) 742 | } 743 | 744 | var optional bool 745 | for _, option := range strings.Split(tag, ",") { 746 | switch strings.TrimSpace(option) { 747 | case "optional": 748 | optional = true 749 | } 750 | } 751 | tag = strings.Split(tag, ",")[0] 752 | 753 | instance, err := injector.getInstanceOfTypeWithAnnotation(field.Type(), tag, nil, optional, circularTrace) 754 | if err != nil { 755 | return wrapErr(err) 756 | } 757 | if instance.Kind() == reflect.Ptr { 758 | if instance.Elem().Kind() == reflect.Func || instance.Elem().Kind() == reflect.Interface || instance.Elem().Kind() == reflect.Slice { 759 | instance = instance.Elem() 760 | } 761 | } 762 | if field.Kind() != reflect.Ptr && field.Kind() != reflect.Interface && instance.Kind() == reflect.Ptr { 763 | if injectionTracing { 764 | slog.Info(fmt.Sprintf("SETTING FIELD: %s of type \"%s\"", currentFieldName, ctype.Field(fieldIndex).Type.String())) 765 | } 766 | 767 | field.Set(instance.Elem()) 768 | } else { 769 | if field.Kind() == reflect.Ptr && field.Type().Kind() == reflect.Ptr && field.Type().Elem().Kind() == reflect.Interface { 770 | return wrapErr(fmt.Errorf("field %#v is pointer to interface. %w", currentFieldName, errPointersToInterface)) 771 | } 772 | 773 | if injectionTracing { 774 | slog.Info(fmt.Sprintf("SETTING FIELD: %s of type \"%s\"", currentFieldName, ctype.Field(fieldIndex).Type.String())) 775 | } 776 | 777 | field.Set(instance) 778 | } 779 | } 780 | } 781 | 782 | case reflect.Interface: 783 | if !current.IsNil() { 784 | injectlist = append(injectlist, current.Elem()) 785 | } 786 | 787 | case reflect.Slice: 788 | for i := 0; i < current.Len(); i++ { 789 | injectlist = append(injectlist, current.Index(i)) 790 | } 791 | 792 | default: 793 | } 794 | } 795 | return nil 796 | } 797 | -------------------------------------------------------------------------------- /dingo_child_test.go: -------------------------------------------------------------------------------- 1 | package dingo 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | type ( 10 | childIface interface{} 11 | childParentIface interface { 12 | child() childIface 13 | } 14 | 15 | childIfaceProvider func() childIface 16 | 17 | childParentIfaceImpl struct { 18 | childInstance childIfaceProvider 19 | } 20 | childIfaceImpl struct{} 21 | ) 22 | 23 | func (i *childParentIfaceImpl) Inject(childInstance childIfaceProvider) { 24 | i.childInstance = childInstance 25 | } 26 | 27 | func (i *childParentIfaceImpl) child() childIface { 28 | return i.childInstance() 29 | } 30 | 31 | func TestChild(t *testing.T) { 32 | injector, err := NewInjector() 33 | assert.NoError(t, err) 34 | injector.Bind(new(childParentIface)).To(new(childParentIfaceImpl)) 35 | 36 | child, err := injector.Child() 37 | assert.NoError(t, err) 38 | child.Bind(new(childIface)).To(new(childIfaceImpl)) 39 | 40 | _, err = injector.GetInstance(new(childParentIface)) 41 | assert.NoError(t, err) 42 | 43 | // we can get an instance in child, because we have a binding here 44 | i, err := child.GetInstance(new(childParentIface)) 45 | assert.NoError(t, err) 46 | 47 | assert.NotNil(t, i.(childParentIface).child()) 48 | } 49 | -------------------------------------------------------------------------------- /dingo_setup_test.go: -------------------------------------------------------------------------------- 1 | package dingo 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | type ( 10 | setupT1 struct { 11 | member1 string 12 | member2 string 13 | member3 string 14 | Member4 string `inject:"annotation4"` 15 | } 16 | ) 17 | 18 | func (s *setupT1) Inject(member1 string, annotated *struct { 19 | Member2 string `inject:"annotation2"` 20 | Member3 string `inject:"annotation3"` 21 | }) { 22 | s.member1 = member1 23 | s.member2 = annotated.Member2 24 | s.member3 = annotated.Member3 25 | } 26 | 27 | func Test_Dingo_Setup(t *testing.T) { 28 | injector, err := NewInjector() 29 | assert.NoError(t, err) 30 | injector.Bind((*string)(nil)).ToInstance("Member 1") 31 | injector.Bind((*string)(nil)).AnnotatedWith("annotation2").ToInstance("Member 2") 32 | injector.Bind((*string)(nil)).AnnotatedWith("annotation3").ToInstance("Member 3") 33 | injector.Bind((*string)(nil)).AnnotatedWith("annotation4").ToInstance("Member 4") 34 | 35 | i, err := injector.GetInstance((*setupT1)(nil)) 36 | assert.NoError(t, err) 37 | test := i.(*setupT1) 38 | 39 | assert.Equal(t, test.member1, "Member 1") 40 | assert.Equal(t, test.member2, "Member 2") 41 | assert.Equal(t, test.member3, "Member 3") 42 | assert.Equal(t, test.Member4, "Member 4") 43 | } 44 | -------------------------------------------------------------------------------- /dingo_test.go: -------------------------------------------------------------------------------- 1 | package dingo 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | type ( 11 | testInterface interface { 12 | Test() int 13 | } 14 | 15 | interfaceSub testInterface 16 | 17 | interfaceImpl1 struct { 18 | foo string 19 | } 20 | 21 | interfaceImpl2 struct{} 22 | 23 | testInterfaceProvider func() testInterface 24 | testInterfaceWithErrorProvider func() (testInterface, error) 25 | 26 | depTest struct { 27 | Iface testInterface `inject:""` 28 | Iface2 testInterface `inject:"test"` 29 | 30 | IfaceProvider testInterfaceProvider `inject:""` 31 | IfaceWithErrorProvider testInterfaceWithErrorProvider `inject:""` 32 | IfaceProvided testInterface `inject:"provider"` 33 | IfaceImpl1Provided testInterface `inject:"providerimpl1"` 34 | IfaceInstance testInterface `inject:"instance"` 35 | } 36 | 37 | testSingleton struct{} 38 | 39 | testModule struct{} 40 | 41 | preTestModule struct{} 42 | ) 43 | 44 | func interfaceProvider(str string) testInterface { 45 | return &interfaceImpl1{foo: str} 46 | } 47 | 48 | func interfaceImpl1Provider(str string) *interfaceImpl1 { 49 | return &interfaceImpl1{foo: str} 50 | } 51 | 52 | func (ptm *preTestModule) Configure(injector *Injector) { 53 | injector.Bind((*string)(nil)).ToInstance("Hello World") 54 | } 55 | 56 | func (tm *testModule) Configure(injector *Injector) { 57 | injector.Bind((*testInterface)(nil)).To((*interfaceSub)(nil)) 58 | injector.Bind((*interfaceSub)(nil)).To(interfaceImpl1{}) 59 | injector.Bind((*testInterface)(nil)).AnnotatedWith("test").To(interfaceImpl2{}) 60 | 61 | injector.Bind((*testInterface)(nil)).AnnotatedWith("provider").ToProvider(interfaceProvider) 62 | injector.Bind((*testInterface)(nil)).AnnotatedWith("providerimpl1").ToProvider(interfaceImpl1Provider) 63 | injector.Bind((*testInterface)(nil)).AnnotatedWith("instance").ToInstance(new(interfaceImpl2)) 64 | 65 | injector.Bind(testSingleton{}).AsEagerSingleton() 66 | } 67 | 68 | func (if1 *interfaceImpl1) Test() int { 69 | return 1 70 | } 71 | 72 | func (if2 *interfaceImpl2) Test() int { 73 | return 2 74 | } 75 | 76 | func TestDingoResolving(t *testing.T) { 77 | t.Run("Should resolve dependencies on request", func(t *testing.T) { 78 | injector, err := NewInjector(new(preTestModule), new(testModule)) 79 | assert.NoError(t, err) 80 | 81 | i, err := injector.GetInstance(new(testInterface)) 82 | assert.NoError(t, err) 83 | var iface testInterface 84 | iface = i.(testInterface) 85 | 86 | assert.Equal(t, 1, iface.Test()) 87 | 88 | i, err = injector.GetInstance(new(depTest)) 89 | assert.NoError(t, err) 90 | dt := *i.(*depTest) 91 | 92 | assert.Equal(t, 1, dt.Iface.Test()) 93 | assert.Equal(t, 2, dt.Iface2.Test()) 94 | 95 | var dt2 depTest 96 | assert.NoError(t, injector.requestInjection(&dt2, nil)) 97 | 98 | assert.Equal(t, 1, dt2.Iface.Test()) 99 | assert.Equal(t, 2, dt2.Iface2.Test()) 100 | 101 | assert.Equal(t, 1, dt.IfaceProvided.Test()) 102 | assert.Equal(t, 1, dt.IfaceImpl1Provided.Test()) 103 | assert.Equal(t, 2, dt.IfaceInstance.Test()) 104 | 105 | assert.Equal(t, 1, dt.IfaceProvider().Test()) 106 | iface, err = dt.IfaceWithErrorProvider() 107 | assert.NoError(t, err) 108 | assert.Equal(t, 1, iface.Test()) 109 | assert.Equal(t, "Hello World", dt.IfaceProvided.(*interfaceImpl1).foo) 110 | assert.Equal(t, "Hello World", dt.IfaceImpl1Provided.(*interfaceImpl1).foo) 111 | }) 112 | 113 | t.Run("Should resolve scopes", func(t *testing.T) { 114 | injector, err := NewInjector(new(testModule)) 115 | assert.NoError(t, err) 116 | 117 | i1, err := injector.GetInstance(testSingleton{}) 118 | assert.NoError(t, err) 119 | i2, err := injector.GetInstance(testSingleton{}) 120 | assert.NoError(t, err) 121 | assert.Equal(t, i1, i2) 122 | }) 123 | 124 | t.Run("Error cases", func(t *testing.T) { 125 | var injector *Injector 126 | _, err := injector.Child() 127 | assert.Error(t, err) 128 | }) 129 | } 130 | 131 | type testBoundNothingProvider func() *interfaceImpl1 132 | 133 | func TestBoundToNothing(t *testing.T) { 134 | injector, err := NewInjector() 135 | assert.NoError(t, err) 136 | 137 | injector.Bind(new(interfaceImpl1)).AnnotatedWith("test") 138 | 139 | i, err := injector.GetInstance(new(testBoundNothingProvider)) 140 | assert.NoError(t, err) 141 | ii, ok := i.(testBoundNothingProvider) 142 | assert.True(t, ok) 143 | assert.NotNil(t, ii) 144 | assert.NotNil(t, ii()) 145 | } 146 | 147 | // interceptors 148 | type ( 149 | AopInterface interface { 150 | Test() string 151 | } 152 | 153 | AopImpl struct{} 154 | 155 | AopDep struct { 156 | A AopInterface `inject:""` 157 | } 158 | 159 | AopInterceptor1 struct { 160 | AopInterface 161 | } 162 | 163 | AopInterceptor2 struct { 164 | AopInterface 165 | } 166 | 167 | AopModule struct{} 168 | ) 169 | 170 | func (m *AopModule) Configure(injector *Injector) { 171 | injector.Bind((*AopInterface)(nil)).To(AopImpl{}) 172 | 173 | injector.BindInterceptor((*AopInterface)(nil), AopInterceptor1{}) 174 | injector.BindInterceptor((*AopInterface)(nil), AopInterceptor2{}) 175 | } 176 | 177 | func (a *AopImpl) Test() string { 178 | return "Test" 179 | } 180 | 181 | func (a *AopInterceptor1) Test() string { 182 | return a.AopInterface.Test() + " 1" 183 | } 184 | 185 | func (a *AopInterceptor2) Test() string { 186 | return a.AopInterface.Test() + " 2" 187 | } 188 | 189 | func TestInterceptors(t *testing.T) { 190 | injector, err := NewInjector(new(AopModule)) 191 | assert.NoError(t, err) 192 | 193 | var dep AopDep 194 | assert.NoError(t, injector.requestInjection(&dep, nil)) 195 | 196 | assert.Equal(t, "Test 1 2", dep.A.Test()) 197 | } 198 | 199 | func TestOptional(t *testing.T) { 200 | type test struct { 201 | Must string `inject:"must"` 202 | Optional string `inject:"option,optional"` 203 | Optional2 string `inject:"option, optional"` 204 | } 205 | 206 | injector, err := NewInjector() 207 | assert.NoError(t, err) 208 | 209 | _, err = injector.GetInstance(new(test)) 210 | assert.Error(t, err) 211 | 212 | injector.Bind(new(string)).AnnotatedWith("must").ToInstance("must") 213 | i, err := injector.GetInstance(new(test)) 214 | assert.NoError(t, err) 215 | assert.Equal(t, i.(*test).Must, "must") 216 | assert.Equal(t, i.(*test).Optional, "") 217 | assert.Equal(t, i.(*test).Optional2, "") 218 | 219 | injector.Bind(new(string)).AnnotatedWith("option").ToInstance("option") 220 | i, err = injector.GetInstance(new(test)) 221 | assert.NoError(t, err) 222 | assert.Equal(t, i.(*test).Must, "must") 223 | assert.Equal(t, i.(*test).Optional, "option") 224 | assert.Equal(t, i.(*test).Optional2, "option") 225 | } 226 | 227 | func TestOverrides(t *testing.T) { 228 | t.Run("not annotated", func(t *testing.T) { 229 | injector, err := NewInjector() 230 | assert.NoError(t, err) 231 | 232 | injector.Bind(new(string)).ToInstance("test") 233 | injector.Bind(new(string)).ToInstance("test-bla") 234 | injector.Override(new(string), "").ToInstance("test2") 235 | assert.NoError(t, injector.InitModules()) 236 | 237 | i, err := injector.GetInstance(new(string)) 238 | assert.NoError(t, err) 239 | 240 | s, ok := i.(string) 241 | assert.True(t, ok) 242 | assert.Equal(t, "test2", s) 243 | }) 244 | 245 | t.Run("annotated", func(t *testing.T) { 246 | injector, err := NewInjector() 247 | assert.NoError(t, err) 248 | 249 | injector.Bind(new(string)).AnnotatedWith("test").ToInstance("test") 250 | injector.Bind(new(string)).AnnotatedWith("test").ToInstance("test-bla") 251 | injector.Override(new(string), "test").ToInstance("test2") 252 | assert.NoError(t, injector.InitModules()) 253 | 254 | i, err := injector.GetAnnotatedInstance(new(string), "test") 255 | assert.NoError(t, err) 256 | 257 | s, ok := i.(string) 258 | assert.True(t, ok) 259 | assert.Equal(t, "test2", s) 260 | }) 261 | } 262 | 263 | func TestProvider(t *testing.T) { 264 | t.Run("Provider", func(t *testing.T) { 265 | injector, err := NewInjector() 266 | assert.NoError(t, err) 267 | 268 | injector.Bind(new(string)).ToProvider(func(i int) string { 269 | return "test" + strconv.Itoa(i) 270 | }) 271 | 272 | i, err := injector.GetInstance(new(string)) 273 | assert.NoError(t, err) 274 | 275 | assert.Equal(t, "test0", i.(string)) 276 | }) 277 | 278 | t.Run("Slice Provider", func(t *testing.T) { 279 | injector, err := NewInjector() 280 | assert.NoError(t, err) 281 | 282 | injector.Bind(new([]string)).ToProvider(func() []string { 283 | return []string{"a", "b"} 284 | }) 285 | 286 | i, err := injector.GetInstance(new([]string)) 287 | assert.NoError(t, err) 288 | 289 | assert.Equal(t, []string{"a", "b"}, i.([]string)) 290 | }) 291 | 292 | t.Run("Invalid Provider", func(t *testing.T) { 293 | injector, err := NewInjector() 294 | assert.NoError(t, err) 295 | 296 | injector.Bind(new(string)).ToProvider(func(interface{}) string { 297 | return "test" 298 | }) 299 | 300 | _, err = injector.GetInstance(new(string)) 301 | assert.Error(t, err) 302 | }) 303 | } 304 | 305 | type testInjectInvalid struct { 306 | A int `inject:"a"` 307 | } 308 | 309 | func (*testInjectInvalid) Configure(*Injector) {} 310 | 311 | func TestInjector_InitModules(t *testing.T) { 312 | injector, err := NewInjector() 313 | assert.NoError(t, err) 314 | assert.Error(t, injector.InitModules(new(testInjectInvalid))) 315 | } 316 | 317 | type TestInjectStructRecInterface interface { 318 | TestXyz() 319 | } 320 | 321 | type testInjectStructRecStruct struct{} 322 | 323 | func (t testInjectStructRecStruct) TestXyz() {} 324 | 325 | func (t testInjectStructRecStruct) Inject() {} 326 | 327 | func TestInjectStructRec(t *testing.T) { 328 | t.Parallel() 329 | 330 | injector, err := NewInjector() 331 | assert.NoError(t, err) 332 | 333 | injector.Bind(new(TestInjectStructRecInterface)).To(new(testInjectStructRecStruct)) 334 | 335 | _, err = injector.GetInstance(new(TestInjectStructRecInterface)) 336 | assert.Error(t, err) 337 | } 338 | 339 | type someStructWithInvalidInterfacePointer struct { 340 | A *testInterface `inject:""` 341 | } 342 | 343 | func TestInjectionOfInterfacePointer(t *testing.T) { 344 | t.Parallel() 345 | 346 | injector, err := NewInjector() 347 | assert.NoError(t, err) 348 | 349 | injector.Bind((*testInterface)(nil)).To(interfaceImpl1{}) 350 | 351 | _, err = injector.GetInstance(new(someStructWithInvalidInterfacePointer)) 352 | assert.Error(t, err, "Expected error") 353 | } 354 | -------------------------------------------------------------------------------- /example/application/service.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "strconv" 7 | ) 8 | 9 | // TransactionLog logs information with a unique id and a message 10 | type TransactionLog interface { 11 | Log(id, message string) 12 | } 13 | 14 | // CreditCardProcessor allows to auth and eventually capture an amount 15 | // float64 is used as an example here 16 | type CreditCardProcessor interface { 17 | Auth(amount float64) error 18 | Capture(amount float64) error 19 | } 20 | 21 | // Service defines our example application service 22 | type Service struct { 23 | logger TransactionLog 24 | processor CreditCardProcessor 25 | } 26 | 27 | // Inject dependencies for our service 28 | func (s *Service) Inject(logger TransactionLog, processor CreditCardProcessor) *Service { 29 | s.logger = logger 30 | s.processor = processor 31 | return s 32 | } 33 | 34 | // MakeTransaction tries to authorize and capture an amount, and logs these steps. 35 | func (s *Service) MakeTransaction(amount float64, message string) error { 36 | id := strconv.Itoa(rand.Int()) 37 | 38 | s.logger.Log(id, fmt.Sprintf("Start transaction %q", message)) 39 | 40 | s.logger.Log(id, "Try to Auth") 41 | if err := s.processor.Auth(amount); err != nil { 42 | s.logger.Log(id, "Auth failed") 43 | return err 44 | } 45 | 46 | s.logger.Log(id, "Try to Capture") 47 | if err := s.processor.Capture(amount); err != nil { 48 | s.logger.Log(id, "Capture failed") 49 | return err 50 | } 51 | 52 | s.logger.Log(id, "Transaction successful") 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "log/slog" 6 | 7 | "flamingo.me/dingo" 8 | "flamingo.me/dingo/example/application" 9 | "flamingo.me/dingo/example/paypal" 10 | ) 11 | 12 | type stdloggerTransactionLog struct { 13 | prefix string 14 | } 15 | 16 | var _ application.TransactionLog = new(stdloggerTransactionLog) 17 | 18 | // Log a message with the configure prefix 19 | func (s *stdloggerTransactionLog) Log(id, message string) { 20 | slog.Info(s.prefix, id, message) 21 | } 22 | 23 | type defaultModule struct{} 24 | 25 | // Configure the dingo injector 26 | func (*defaultModule) Configure(injector *dingo.Injector) { 27 | injector.Bind(new(application.TransactionLog)).ToInstance(&stdloggerTransactionLog{ 28 | prefix: "example", 29 | }) 30 | } 31 | 32 | func main() { 33 | // create a new injector and load modules 34 | injector, err := dingo.NewInjector( 35 | new(paypal.Module), 36 | new(defaultModule), 37 | ) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | 42 | // instantiate the application service 43 | service, err := injector.GetInstance(application.Service{}) 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | 48 | // make a transaction 49 | if err := service.(*application.Service).MakeTransaction(99.95, "test transaction"); err != nil { 50 | log.Fatal(err) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /example/paypal/module.go: -------------------------------------------------------------------------------- 1 | package paypal 2 | 3 | import ( 4 | "flamingo.me/dingo" 5 | "flamingo.me/dingo/example/application" 6 | ) 7 | 8 | // Module configures an application to use the paypalCCProcessor for CreditCardProcessing 9 | type Module struct{} 10 | 11 | // Configure dependency injection 12 | func (m *Module) Configure(injector *dingo.Injector) { 13 | injector.Bind(new(application.CreditCardProcessor)).To(new(paypalCCProcessor)) 14 | } 15 | -------------------------------------------------------------------------------- /example/paypal/processor.go: -------------------------------------------------------------------------------- 1 | package paypal 2 | 3 | import ( 4 | "log" 5 | 6 | "flamingo.me/dingo/example/application" 7 | ) 8 | 9 | type paypalCCProcessor struct{} 10 | 11 | var _ application.CreditCardProcessor = new(paypalCCProcessor) 12 | 13 | func (*paypalCCProcessor) Auth(amount float64) error { 14 | log.Printf("Paypal: Auth: %v", amount) 15 | return nil 16 | } 17 | 18 | func (*paypalCCProcessor) Capture(amount float64) error { 19 | log.Printf("Paypal: Capture: %v", amount) 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module flamingo.me/dingo 2 | 3 | go 1.23 4 | 5 | require github.com/stretchr/testify v1.10.0 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 6 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 9 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 10 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 11 | -------------------------------------------------------------------------------- /inspect.go: -------------------------------------------------------------------------------- 1 | package dingo 2 | 3 | import "reflect" 4 | 5 | // Inspector defines callbacks called during injector inspection 6 | type Inspector struct { 7 | InspectBinding func(of reflect.Type, annotation string, to reflect.Type, provider, instance *reflect.Value, in Scope) 8 | InspectMultiBinding func(of reflect.Type, index int, annotation string, to reflect.Type, provider, instance *reflect.Value, in Scope) 9 | InspectMapBinding func(of reflect.Type, key string, annotation string, to reflect.Type, provider, instance *reflect.Value, in Scope) 10 | InspectParent func(parent *Injector) 11 | } 12 | 13 | // Inspect the injector 14 | func (injector *Injector) Inspect(inspector Inspector) { 15 | if inspector.InspectBinding != nil { 16 | for t, bindings := range injector.bindings { 17 | for _, binding := range bindings { 18 | var pfnc *reflect.Value 19 | if binding.provider != nil { 20 | pfnc = &binding.provider.fnc 21 | } 22 | var ival *reflect.Value 23 | if binding.instance != nil { 24 | ival = &binding.instance.ivalue 25 | } 26 | inspector.InspectBinding(t, binding.annotatedWith, binding.to, pfnc, ival, binding.scope) 27 | } 28 | } 29 | } 30 | 31 | if inspector.InspectMultiBinding != nil { 32 | for t, bindings := range injector.multibindings { 33 | for i, binding := range bindings { 34 | var pfnc *reflect.Value 35 | if binding.provider != nil { 36 | pfnc = &binding.provider.fnc 37 | } 38 | var ival *reflect.Value 39 | if binding.instance != nil { 40 | ival = &binding.instance.ivalue 41 | } 42 | inspector.InspectMultiBinding(t, i, binding.annotatedWith, binding.to, pfnc, ival, binding.scope) 43 | } 44 | } 45 | } 46 | 47 | if inspector.InspectMapBinding != nil { 48 | for t, bindings := range injector.mapbindings { 49 | for key, binding := range bindings { 50 | var pfnc *reflect.Value 51 | if binding.provider != nil { 52 | pfnc = &binding.provider.fnc 53 | } 54 | var ival *reflect.Value 55 | if binding.instance != nil { 56 | ival = &binding.instance.ivalue 57 | } 58 | inspector.InspectMapBinding(t, key, binding.annotatedWith, binding.to, pfnc, ival, binding.scope) 59 | } 60 | } 61 | } 62 | 63 | if inspector.InspectParent != nil && injector.parent != nil { 64 | inspector.InspectParent(injector.parent) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /miniexample/logger/service.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | // Logger logs a message 4 | type Logger interface { 5 | Log(message string) 6 | } 7 | 8 | // LogService is a sample service to demonstrate logging 9 | type LogService struct { 10 | logger Logger 11 | } 12 | 13 | // Inject load our dependencies 14 | func (s *LogService) Inject(logger Logger) *LogService { 15 | s.logger = logger 16 | return s 17 | } 18 | 19 | // DoLog does a sample log 20 | func (s *LogService) DoLog(message string) { 21 | s.logger.Log(message) 22 | } 23 | -------------------------------------------------------------------------------- /miniexample/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "log/slog" 6 | 7 | "flamingo.me/dingo" 8 | "flamingo.me/dingo/miniexample/logger" 9 | ) 10 | 11 | type stdLogger struct{} 12 | 13 | // Log logs a message 14 | func (s *stdLogger) Log(message string) { 15 | slog.Info(message) 16 | } 17 | 18 | type loggerModule struct{} 19 | 20 | // Configure configures the dingo injector 21 | func (*loggerModule) Configure(injector *dingo.Injector) { 22 | injector.Bind(new(logger.Logger)).ToInstance(&stdLogger{}) 23 | } 24 | 25 | func main() { 26 | // create a new injector 27 | injector, err := dingo.NewInjector( 28 | new(loggerModule), 29 | ) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | 34 | // instantiate the log service 35 | service, err := injector.GetInstance(logger.LogService{}) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | // do a sample log using our service 41 | service.(*logger.LogService).DoLog("here is an example log") 42 | } 43 | -------------------------------------------------------------------------------- /module.go: -------------------------------------------------------------------------------- 1 | package dingo 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | type ( 9 | // Module is default entry point for dingo Modules. 10 | // The Configure method is called once during initialization 11 | // and let's the module setup Bindings for the provided Injector. 12 | Module interface { 13 | Configure(injector *Injector) 14 | } 15 | 16 | // ModuleFunc wraps a func(injector *Injector) for dependency injection. 17 | // This allows using small functions as dingo Modules. 18 | // The same concept is http.HandlerFunc for http.Handler. 19 | ModuleFunc func(injector *Injector) 20 | 21 | // Depender returns a list of Modules via the Depends method. 22 | // This allows a module to specify dependencies, which will be loaded before the actual Module is loaded. 23 | Depender interface { 24 | Depends() []Module 25 | } 26 | ) 27 | 28 | // Configure call the original ModuleFunc with the given *Injector. 29 | func (f ModuleFunc) Configure(injector *Injector) { 30 | f(injector) 31 | } 32 | 33 | // TryModule tests if modules are properly bound 34 | func TryModule(modules ...Module) (resultingError error) { 35 | defer func() { 36 | if err := recover(); err != nil { 37 | if err, ok := err.(error); ok { 38 | resultingError = err 39 | return 40 | } 41 | resultingError = fmt.Errorf("dingo.TryModule panic: %q", err) 42 | } 43 | }() 44 | 45 | injector, err := NewInjector() 46 | if err != nil { 47 | return err 48 | } 49 | injector.buildEagerSingletons = false 50 | return injector.InitModules(modules...) 51 | } 52 | 53 | var typeOfModuleFunc = reflect.TypeOf(ModuleFunc(nil)) 54 | 55 | // resolveDependencies tries to get a complete list of all modules, including all dependencies 56 | // known can be empty initially, and will then be used for subsequent recursive calls 57 | func resolveDependencies(modules []Module, known map[interface{}]struct{}) []Module { 58 | final := make([]Module, 0, len(modules)) 59 | 60 | if known == nil { 61 | known = make(map[interface{}]struct{}) 62 | } 63 | 64 | for _, module := range modules { 65 | var identity interface{} = reflect.TypeOf(module) 66 | if identity == typeOfModuleFunc { 67 | identity = reflect.ValueOf(module) 68 | } 69 | if _, ok := known[identity]; ok { 70 | continue 71 | } 72 | known[identity] = struct{}{} 73 | if depender, ok := module.(Depender); ok { 74 | final = append(final, resolveDependencies(depender.Depends(), known)...) 75 | } 76 | final = append(final, module) 77 | } 78 | 79 | return final 80 | } 81 | -------------------------------------------------------------------------------- /module_test.go: -------------------------------------------------------------------------------- 1 | package dingo 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | type ( 11 | tryModuleOk struct{} 12 | tryModuleFail struct{} 13 | tryModulePanic struct{} 14 | ) 15 | 16 | func (t *tryModuleOk) Configure(injector *Injector) { 17 | injector.Bind(new(string)).ToInstance("test") 18 | } 19 | 20 | func (t *tryModuleFail) Configure(injector *Injector) { 21 | injector.Bind(new(int)).ToInstance("test") 22 | } 23 | 24 | func (t *tryModulePanic) Configure(injector *Injector) { 25 | injector.Bind(nil) 26 | } 27 | 28 | func TestTryModule(t *testing.T) { 29 | assert.NoError(t, TryModule(new(tryModuleOk))) 30 | 31 | assert.Error(t, TryModule(new(tryModuleFail))) 32 | 33 | assert.Error(t, TryModule(new(tryModulePanic))) 34 | } 35 | 36 | func TestResolveDependenciesWithModuleFunc(t *testing.T) { 37 | var countInline, countExtern int 38 | 39 | ext := ModuleFunc(func(injector *Injector) { 40 | countExtern++ 41 | }) 42 | 43 | injector, err := NewInjector( 44 | new(tryModuleOk), 45 | ModuleFunc(func(injector *Injector) { 46 | countInline++ 47 | }), 48 | ModuleFunc(func(injector *Injector) { 49 | countInline++ 50 | }), 51 | ext, 52 | ext, 53 | ) 54 | 55 | assert.NoError(t, err) 56 | assert.NotNil(t, injector) 57 | assert.Equal(t, 2, countInline, "inline modules should be called once (eventually twice for this test)") 58 | assert.Equal(t, 1, countExtern, "variable defined modules should only be called once") 59 | } 60 | 61 | type ( 62 | resolveDependenciesModuleA struct{} 63 | resolveDependenciesModuleB struct{} 64 | resolveDependenciesModuleB2 struct{} 65 | resolveDependenciesModuleC struct{} 66 | ) 67 | 68 | func (*resolveDependenciesModuleA) Configure(*Injector) {} 69 | func (*resolveDependenciesModuleA) Depends() []Module { 70 | return []Module{ 71 | new(resolveDependenciesModuleA), 72 | new(resolveDependenciesModuleB), 73 | new(resolveDependenciesModuleB2), 74 | } 75 | } 76 | func (*resolveDependenciesModuleB) Configure(*Injector) {} 77 | func (*resolveDependenciesModuleB) Depends() []Module { 78 | return []Module{ 79 | new(resolveDependenciesModuleC), 80 | new(resolveDependenciesModuleB2), 81 | } 82 | } 83 | func (*resolveDependenciesModuleB2) Configure(*Injector) {} 84 | func (*resolveDependenciesModuleC) Configure(*Injector) {} 85 | 86 | func Test_resolveDependencies(t *testing.T) { 87 | resolved := resolveDependencies([]Module{new(resolveDependenciesModuleA)}, nil) 88 | 89 | if !reflect.DeepEqual(resolved, []Module{ 90 | new(resolveDependenciesModuleC), 91 | new(resolveDependenciesModuleB2), 92 | new(resolveDependenciesModuleB), 93 | new(resolveDependenciesModuleA), 94 | }) { 95 | t.Errorf("%#v not correctly resolved", resolved) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /multi_dingo_test.go: -------------------------------------------------------------------------------- 1 | package dingo 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | type ( 10 | mapBindInterface interface{} 11 | 12 | mapBindInterfaceProvider func() map[string]mapBindInterface 13 | 14 | mapBindTest1 struct { 15 | Mbp mapBindInterfaceProvider `inject:""` 16 | } 17 | 18 | mapBindTest2 struct { 19 | Mb mapBindInterface `inject:"map:testkey"` 20 | } 21 | 22 | mapBindTest3Provider func() mapBindInterface 23 | mapBindTest3MapProvider func() map[string]mapBindTest3Provider 24 | mapBindTest3 struct { 25 | Mbp mapBindTest3MapProvider `inject:""` 26 | } 27 | 28 | multiBindProvider func() mapBindInterface 29 | listmultiBindProvider func() []multiBindProvider 30 | multiBindProviderTest struct { 31 | Mbp listmultiBindProvider `inject:""` 32 | } 33 | multiBindTest struct { 34 | Mb []mapBindInterface `inject:""` 35 | } 36 | ) 37 | 38 | func TestMultiBinding(t *testing.T) { 39 | injector, err := NewInjector() 40 | assert.NoError(t, err) 41 | 42 | injector.BindMulti((*mapBindInterface)(nil)).ToInstance("testkey instance") 43 | injector.BindMulti((*mapBindInterface)(nil)).ToInstance("testkey2 instance") 44 | injector.BindMulti((*mapBindInterface)(nil)).ToInstance("testkey3 instance") 45 | 46 | i, err := injector.GetInstance(&multiBindTest{}) 47 | assert.NoError(t, err) 48 | test := i.(*multiBindTest) 49 | list := test.Mb 50 | 51 | assert.Len(t, list, 3) 52 | 53 | assert.Equal(t, "testkey instance", list[0]) 54 | assert.Equal(t, "testkey2 instance", list[1]) 55 | assert.Equal(t, "testkey3 instance", list[2]) 56 | } 57 | 58 | func TestMultiBindingChild(t *testing.T) { 59 | injector, err := NewInjector() 60 | assert.NoError(t, err) 61 | 62 | injector.BindMulti((*mapBindInterface)(nil)).ToInstance("testkey instance") 63 | injector.BindMulti((*mapBindInterface)(nil)).ToInstance("testkey2 instance") 64 | injector.BindMulti((*mapBindInterface)(nil)).ToInstance("testkey3 instance") 65 | 66 | child, err := injector.Child() 67 | assert.NoError(t, err) 68 | child.BindMulti((*mapBindInterface)(nil)).ToInstance("testkey4 instance") 69 | 70 | i, err := injector.GetInstance(&multiBindTest{}) 71 | assert.NoError(t, err) 72 | test := i.(*multiBindTest) 73 | list := test.Mb 74 | 75 | assert.Len(t, list, 3) 76 | 77 | assert.Equal(t, "testkey instance", list[0]) 78 | assert.Equal(t, "testkey2 instance", list[1]) 79 | assert.Equal(t, "testkey3 instance", list[2]) 80 | 81 | i, err = child.GetInstance(&multiBindTest{}) 82 | assert.NoError(t, err) 83 | test = i.(*multiBindTest) 84 | list = test.Mb 85 | 86 | assert.Len(t, list, 4) 87 | 88 | assert.Equal(t, "testkey instance", list[0]) 89 | assert.Equal(t, "testkey2 instance", list[1]) 90 | assert.Equal(t, "testkey3 instance", list[2]) 91 | assert.Equal(t, "testkey4 instance", list[3]) 92 | } 93 | 94 | func TestMultiBindingProvider(t *testing.T) { 95 | injector, err := NewInjector() 96 | assert.NoError(t, err) 97 | 98 | injector.BindMulti((*mapBindInterface)(nil)).ToInstance("testkey instance") 99 | injector.BindMulti((*mapBindInterface)(nil)).ToInstance("testkey2 instance") 100 | injector.BindMulti((*mapBindInterface)(nil)).ToInstance("testkey3 instance") 101 | 102 | i, err := injector.GetInstance(&multiBindProviderTest{}) 103 | assert.NoError(t, err) 104 | test := i.(*multiBindProviderTest) 105 | list := test.Mbp() 106 | 107 | assert.Len(t, list, 3) 108 | 109 | assert.Equal(t, "testkey instance", list[0]()) 110 | assert.Equal(t, "testkey2 instance", list[1]()) 111 | assert.Equal(t, "testkey3 instance", list[2]()) 112 | } 113 | 114 | func TestMultiBindingComplex(t *testing.T) { 115 | injector, err := NewInjector() 116 | assert.NoError(t, err) 117 | 118 | injector.BindMulti((*mapBindInterface)(nil)).ToInstance("testkey instance") 119 | injector.BindMulti((*mapBindInterface)(nil)).To("testkey2 instance") 120 | injector.BindMulti((*mapBindInterface)(nil)).ToProvider(func() mapBindInterface { return "provided" }) 121 | 122 | i, err := injector.GetInstance(&multiBindTest{}) 123 | assert.NoError(t, err) 124 | test := i.(*multiBindTest) 125 | list := test.Mb 126 | 127 | assert.Len(t, list, 3) 128 | 129 | assert.Equal(t, "testkey instance", list[0]) 130 | assert.NotNil(t, list[1]) 131 | assert.Equal(t, "provided", list[2]) 132 | } 133 | 134 | func TestMultiBindingComplexProvider(t *testing.T) { 135 | injector, err := NewInjector() 136 | assert.NoError(t, err) 137 | 138 | injector.BindMulti((*mapBindInterface)(nil)).ToInstance("testkey instance") 139 | injector.BindMulti((*mapBindInterface)(nil)).To("testkey2 instance") 140 | injector.BindMulti((*mapBindInterface)(nil)).ToProvider(func() mapBindInterface { return "provided" }) 141 | 142 | i, err := injector.GetInstance(&multiBindProviderTest{}) 143 | assert.NoError(t, err) 144 | test := i.(*multiBindProviderTest) 145 | list := test.Mbp() 146 | 147 | assert.Len(t, list, 3) 148 | 149 | assert.Equal(t, "testkey instance", list[0]()) 150 | assert.NotNil(t, list[1]()) 151 | assert.Equal(t, "provided", list[2]()) 152 | } 153 | 154 | func TestMapBinding(t *testing.T) { 155 | injector, err := NewInjector() 156 | assert.NoError(t, err) 157 | 158 | injector.BindMap((*mapBindInterface)(nil), "testkey").ToInstance("testkey instance") 159 | injector.BindMap((*mapBindInterface)(nil), "testkey2").ToInstance("testkey2 instance") 160 | injector.BindMap((*mapBindInterface)(nil), "testkey3").ToInstance("testkey3 instance") 161 | 162 | i, err := injector.GetInstance(&mapBindTest1{}) 163 | assert.NoError(t, err) 164 | test1 := i.(*mapBindTest1) 165 | test1map := test1.Mbp() 166 | 167 | assert.Len(t, test1map, 3) 168 | assert.Equal(t, "testkey instance", test1map["testkey"]) 169 | assert.Equal(t, "testkey2 instance", test1map["testkey2"]) 170 | assert.Equal(t, "testkey3 instance", test1map["testkey3"]) 171 | 172 | i, err = injector.GetInstance(&mapBindTest2{}) 173 | assert.NoError(t, err) 174 | test2 := i.(*mapBindTest2) 175 | assert.Equal(t, test2.Mb, "testkey instance") 176 | } 177 | 178 | func TestMapBindingChild(t *testing.T) { 179 | injector, err := NewInjector() 180 | assert.NoError(t, err) 181 | 182 | injector.BindMap((*mapBindInterface)(nil), "testkey").ToInstance("testkey instance") 183 | injector.BindMap((*mapBindInterface)(nil), "testkey2").ToInstance("testkey2 instance") 184 | injector.BindMap((*mapBindInterface)(nil), "testkey3").ToInstance("testkey3 instance") 185 | 186 | child, err := injector.Child() 187 | assert.NoError(t, err) 188 | child.BindMap((*mapBindInterface)(nil), "testkey4").ToInstance("testkey4 instance") 189 | 190 | i, err := injector.GetInstance(&mapBindTest1{}) 191 | assert.NoError(t, err) 192 | test1 := i.(*mapBindTest1) 193 | test1map := test1.Mbp() 194 | 195 | assert.Len(t, test1map, 3) 196 | assert.Equal(t, "testkey instance", test1map["testkey"]) 197 | assert.Equal(t, "testkey2 instance", test1map["testkey2"]) 198 | assert.Equal(t, "testkey3 instance", test1map["testkey3"]) 199 | 200 | i, err = injector.GetInstance(&mapBindTest2{}) 201 | assert.NoError(t, err) 202 | test2 := i.(*mapBindTest2) 203 | assert.Equal(t, test2.Mb, "testkey instance") 204 | 205 | i, err = child.GetInstance(&mapBindTest1{}) 206 | assert.NoError(t, err) 207 | testChild := i.(*mapBindTest1) 208 | testChildmap := testChild.Mbp() 209 | 210 | assert.Len(t, testChildmap, 4) 211 | assert.Equal(t, "testkey instance", testChildmap["testkey"]) 212 | assert.Equal(t, "testkey2 instance", testChildmap["testkey2"]) 213 | assert.Equal(t, "testkey3 instance", testChildmap["testkey3"]) 214 | assert.Equal(t, "testkey4 instance", testChildmap["testkey4"]) 215 | } 216 | 217 | func TestMapBindingProvider(t *testing.T) { 218 | injector, err := NewInjector() 219 | assert.NoError(t, err) 220 | 221 | injector.BindMap((*mapBindInterface)(nil), "testkey").ToInstance("testkey instance") 222 | injector.BindMap((*mapBindInterface)(nil), "testkey2").ToInstance("testkey2 instance") 223 | injector.BindMap((*mapBindInterface)(nil), "testkey3").ToInstance("testkey3 instance") 224 | 225 | i, err := injector.GetInstance(&mapBindTest3{}) 226 | assert.NoError(t, err) 227 | test := i.(*mapBindTest3) 228 | testmap := test.Mbp() 229 | 230 | assert.Len(t, testmap, 3) 231 | assert.Equal(t, "testkey instance", testmap["testkey"]()) 232 | assert.Equal(t, "testkey2 instance", testmap["testkey2"]()) 233 | assert.Equal(t, "testkey3 instance", testmap["testkey3"]()) 234 | } 235 | 236 | func TestMapBindingSingleton(t *testing.T) { 237 | injector, err := NewInjector() 238 | assert.NoError(t, err) 239 | 240 | injector.BindMap(new(mapBindInterface), "a").To("a").In(Singleton) 241 | injector.BindMap(new(mapBindInterface), "b").To("b") 242 | 243 | i, err := injector.GetInstance(new(mapBindTest1)) 244 | assert.NoError(t, err) 245 | 246 | first := i.(*mapBindTest1).Mbp()["a"] 247 | second := i.(*mapBindTest1).Mbp()["a"] 248 | 249 | assert.True(t, first == second) 250 | 251 | first = i.(*mapBindTest1).Mbp()["b"] 252 | second = i.(*mapBindTest1).Mbp()["b"] 253 | 254 | assert.False(t, first == second) 255 | } 256 | 257 | func TestMultiBindingSingleton(t *testing.T) { 258 | injector, err := NewInjector() 259 | assert.NoError(t, err) 260 | 261 | injector.BindMulti(new(mapBindInterface)).To("a").In(Singleton) 262 | 263 | i, err := injector.GetInstance(new(multiBindTest)) 264 | assert.NoError(t, err) 265 | first := i.(*multiBindTest).Mb[0] 266 | 267 | i, err = injector.GetInstance(new(multiBindTest)) 268 | assert.NoError(t, err) 269 | second := i.(*multiBindTest).Mb[0] 270 | 271 | assert.Same(t, first, second) 272 | } 273 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | ":semanticCommitTypeAll(chore)" 5 | ], 6 | "regexManagers": [ 7 | { 8 | "description": "Update go run/go install dependencies", 9 | "fileMatch": [".*\\.go$", "Makefile"], 10 | "matchStrings": [ 11 | "\\s*go (run|install) (?([^@]+)?).*@(?[^\\s]+)" 12 | ], 13 | "datasourceTemplate": "go" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /scope.go: -------------------------------------------------------------------------------- 1 | package dingo 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | ) 7 | 8 | type ( 9 | // Scope defines a scope's behaviour 10 | Scope interface { 11 | ResolveType(t reflect.Type, annotation string, unscoped func(t reflect.Type, annotation string, optional bool) (reflect.Value, error)) (reflect.Value, error) 12 | } 13 | 14 | identifier struct { 15 | t reflect.Type 16 | a string 17 | } 18 | 19 | // SingletonScope is our Scope to handle Singletons 20 | // todo use RWMutex for proper locking 21 | SingletonScope struct { 22 | mu sync.Mutex // lock guarding instaceLocks 23 | instanceLock map[identifier]*sync.RWMutex // lock guarding instances 24 | instances sync.Map 25 | } 26 | 27 | // ChildSingletonScope manages child-specific singleton 28 | ChildSingletonScope SingletonScope 29 | ) 30 | 31 | var ( 32 | // Singleton is the default SingletonScope for dingo 33 | Singleton Scope = NewSingletonScope() 34 | 35 | // ChildSingleton is a per-child singleton, means singletons are scoped and local to an injector instance 36 | ChildSingleton Scope = NewChildSingletonScope() 37 | ) 38 | 39 | // NewSingletonScope creates a new singleton scope 40 | func NewSingletonScope() *SingletonScope { 41 | return &SingletonScope{instanceLock: make(map[identifier]*sync.RWMutex)} 42 | } 43 | 44 | // NewChildSingletonScope creates a new child singleton scope 45 | func NewChildSingletonScope() *ChildSingletonScope { 46 | return &ChildSingletonScope{instanceLock: make(map[identifier]*sync.RWMutex)} 47 | } 48 | 49 | // ResolveType resolves a request in this scope 50 | func (s *SingletonScope) ResolveType(t reflect.Type, annotation string, unscoped func(t reflect.Type, annotation string, optional bool) (reflect.Value, error)) (reflect.Value, error) { 51 | ident := identifier{t, annotation} 52 | 53 | // try to get the instance type lock 54 | s.mu.Lock() 55 | 56 | if l, ok := s.instanceLock[ident]; ok { 57 | // we have the instance lock 58 | s.mu.Unlock() 59 | l.RLock() 60 | defer l.RUnlock() 61 | 62 | instance, _ := s.instances.Load(ident) 63 | return instance.(reflect.Value), nil 64 | } 65 | 66 | s.instanceLock[ident] = new(sync.RWMutex) 67 | l := s.instanceLock[ident] 68 | l.Lock() 69 | s.mu.Unlock() 70 | 71 | instance, err := unscoped(t, annotation, false) 72 | s.instances.Store(ident, instance) 73 | 74 | defer l.Unlock() 75 | 76 | return instance, err 77 | } 78 | 79 | // ResolveType delegates to SingletonScope.ResolveType 80 | func (c *ChildSingletonScope) ResolveType(t reflect.Type, annotation string, unscoped func(t reflect.Type, annotation string, optional bool) (reflect.Value, error)) (reflect.Value, error) { 81 | return (*SingletonScope)(c).ResolveType(t, annotation, unscoped) 82 | } 83 | -------------------------------------------------------------------------------- /scope_test.go: -------------------------------------------------------------------------------- 1 | package dingo 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "runtime" 7 | "sync" 8 | "sync/atomic" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func testScope(t *testing.T, scope Scope) { 15 | var requestedUnscoped int64 16 | 17 | test := reflect.TypeOf(new(string)) 18 | test2 := reflect.TypeOf(new(int)) 19 | 20 | unscoped := func(t reflect.Type, annotation string, optional bool) (reflect.Value, error) { 21 | atomic.AddInt64(&requestedUnscoped, 1) 22 | 23 | runtime.Gosched() 24 | 25 | if optional { 26 | return reflect.Value{}, nil 27 | } 28 | return reflect.New(t).Elem(), nil 29 | } 30 | 31 | runs := 1000 // change to 10? 100? 1000? to trigger a bug? todo investigate 32 | 33 | wg := new(sync.WaitGroup) 34 | wg.Add(runs) 35 | for i := 0; i < runs; i++ { 36 | go func() { 37 | t1, err := scope.ResolveType(test, "", unscoped) 38 | assert.NoError(t, err) 39 | t12, err := scope.ResolveType(test2, "", unscoped) 40 | assert.NoError(t, err) 41 | t2, err := scope.ResolveType(test, "", unscoped) 42 | assert.NoError(t, err) 43 | t22, err := scope.ResolveType(test2, "", unscoped) 44 | assert.NoError(t, err) 45 | assert.Same(t, t1.Interface(), t2.Interface()) 46 | assert.Same(t, t12.Interface(), t22.Interface()) 47 | wg.Done() 48 | }() 49 | } 50 | wg.Wait() 51 | 52 | // should be 2, one for each type 53 | assert.Equal(t, int64(2), requestedUnscoped) 54 | 55 | } 56 | 57 | func TestSingleton_ResolveType(t *testing.T) { 58 | // reset instance 59 | Singleton = NewSingletonScope() 60 | 61 | testScope(t, Singleton) 62 | } 63 | 64 | func TestChildSingleton_ResolveType(t *testing.T) { 65 | // reset instance 66 | ChildSingleton = NewChildSingletonScope() 67 | 68 | testScope(t, ChildSingleton) 69 | } 70 | 71 | type ( 72 | singletonA struct { 73 | B *singletonB `inject:""` 74 | } 75 | 76 | singletonB struct { 77 | C *singletonC `inject:""` 78 | } 79 | 80 | singletonC string 81 | ) 82 | 83 | func TestScopeWithSubDependencies(t *testing.T) { 84 | sc := singletonC("singleton C") 85 | scp := &sc 86 | for i := 0; i < 10; i++ { 87 | t.Run(fmt.Sprintf("Run %d", i), func(t *testing.T) { 88 | injector, err := NewInjector() 89 | assert.NoError(t, err) 90 | 91 | injector.Bind(new(singletonA)).In(Singleton) 92 | injector.Bind(new(singletonB)).In(Singleton) 93 | injector.Bind(new(singletonC)).In(Singleton).ToInstance(scp) 94 | 95 | runs := 100 96 | 97 | wg := new(sync.WaitGroup) 98 | wg.Add(runs) 99 | for i := 0; i < runs; i++ { 100 | go func() { 101 | i, err := injector.GetInstance(new(singletonA)) 102 | assert.NoError(t, err) 103 | a := i.(*singletonA) 104 | assert.Same(t, a.B.C, scp) 105 | wg.Done() 106 | }() 107 | } 108 | wg.Wait() 109 | }) 110 | } 111 | } 112 | 113 | type inheritedScopeIface interface{} 114 | type inheritedScopeStruct struct{} 115 | type inheritedScopeInjected struct { 116 | i inheritedScopeIface 117 | s *inheritedScopeStruct 118 | } 119 | 120 | func (s *inheritedScopeInjected) Inject(ss *inheritedScopeStruct, si inheritedScopeIface) { 121 | s.s = ss 122 | s.i = si 123 | } 124 | 125 | func TestInheritedScope(t *testing.T) { 126 | injector, err := NewInjector() 127 | assert.NoError(t, err) 128 | 129 | injector.Bind(new(inheritedScopeStruct)).In(ChildSingleton) 130 | injector.Bind(new(inheritedScopeIface)).To(new(inheritedScopeStruct)) 131 | 132 | injector, err = injector.Child() 133 | assert.NoError(t, err) 134 | 135 | i, err := injector.GetInstance(new(inheritedScopeInjected)) 136 | assert.NoError(t, err) 137 | firstS := i.(*inheritedScopeInjected) 138 | i, err = injector.GetInstance(new(inheritedScopeInjected)) 139 | assert.NoError(t, err) 140 | secondS := i.(*inheritedScopeInjected) 141 | assert.Same(t, firstS.s, secondS.s) 142 | 143 | i, err = injector.GetInstance(new(inheritedScopeInjected)) 144 | assert.NoError(t, err) 145 | firstI := i.(*inheritedScopeInjected) 146 | i, err = injector.GetInstance(new(inheritedScopeInjected)) 147 | assert.NoError(t, err) 148 | secondI := i.(*inheritedScopeInjected) 149 | assert.Same(t, firstI.i, secondI.i) 150 | } 151 | --------------------------------------------------------------------------------