├── .codecov.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md └── workflows │ ├── fossa.yaml │ └── go.yml ├── .gitignore ├── .golangci.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── before_callback.go ├── callback.go ├── constructor.go ├── constructor_test.go ├── container.go ├── container_test.go ├── cycle_error.go ├── decorate.go ├── decorate_test.go ├── dig_int_test.go ├── dig_test.go ├── doc.go ├── error.go ├── error_test.go ├── example_test.go ├── go.mod ├── go.sum ├── graph.go ├── group.go ├── group_test.go ├── inout.go ├── internal ├── digclock │ ├── clock.go │ └── clock_test.go ├── digerror │ └── errors.go ├── digreflect │ ├── func.go │ ├── func_test.go │ └── tests │ │ └── myrepository.git │ │ ├── hello.go │ │ └── mypackage │ │ └── add.go ├── digtest │ ├── container.go │ └── doc.go ├── dot │ ├── README.md │ ├── graph.go │ └── graph_test.go └── graph │ ├── graph.go │ └── graph_test.go ├── invoke.go ├── param.go ├── param_test.go ├── provide.go ├── provide_test.go ├── result.go ├── result_test.go ├── scope.go ├── scope_int_test.go ├── scope_test.go ├── stringer_test.go ├── testdata ├── dig_as_two.dot ├── empty.dot ├── error.dot ├── grouped.dot ├── missing.dot ├── missingDep.dot ├── named.dot ├── optional.dot ├── prune_constructor_result.dot ├── prune_non_root_nodes.dot └── simple.dot ├── version.go ├── visualize.go ├── visualize_golden_test.go ├── visualize_int_test.go └── visualize_test.go /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: 70..98 3 | round: down 4 | precision: 2 5 | 6 | status: 7 | project: # measuring the overall project coverage 8 | default: # context, you can create multiple ones with custom titles 9 | enabled: yes # must be yes|true to enable this status 10 | target: 97 # specify the target coverage for each commit status 11 | # option: "auto" (must increase from parent commit or pull request base) 12 | # option: "X%" a static target percentage to hit 13 | if_not_found: success # if parent is not found report status as success, error, or failure 14 | if_ci_failed: error # if ci fails report status as success, error, or failure 15 | 16 | patch: 17 | default: 18 | enabled: yes 19 | target: 70 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Additional context** 20 | Add any other context about the problem here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Questions 3 | about: Please use our Discussions page 4 | url: https://github.com/uber-go/dig/discussions 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Is this a breaking change?** 19 | We do not accept breaking changes to the existing API. Please consider if your proposed solution is backwards compatible. If not, we can help you make it backwards compatible, but this must be considered when we consider new features. 20 | 21 | **Additional context** 22 | Add any other context or screenshots about the feature request here. 23 | -------------------------------------------------------------------------------- /.github/workflows/fossa.yaml: -------------------------------------------------------------------------------- 1 | name: FOSSA Analysis 2 | on: push 3 | 4 | permissions: 5 | contents: read 6 | 7 | jobs: 8 | 9 | build: 10 | runs-on: ubuntu-latest 11 | if: github.repository_owner == 'uber-go' 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | 16 | - name: FOSSA analysis 17 | uses: fossas/fossa-action@v1 18 | with: 19 | api-key: ${{ secrets.FOSSA_API_KEY }} 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: ['*'] 6 | tags: ['v*'] 7 | pull_request: 8 | branches: ['*'] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | 15 | build: 16 | runs-on: ${{ matrix.os }} 17 | name: Test (Go ${{ matrix.go }} / ${{ matrix.os }}) 18 | strategy: 19 | matrix: 20 | os: ["ubuntu-latest", "windows-latest"] 21 | go: ["1.23.x", "1.24.x"] 22 | 23 | steps: 24 | - name: Setup Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: ${{ matrix.go }} 28 | 29 | - name: Checkout code 30 | uses: actions/checkout@v4 31 | 32 | - name: Download Dependencies 33 | run: go mod download 34 | 35 | - name: Test 36 | run: make cover 37 | 38 | - name: Upload coverage to codecov.io 39 | uses: codecov/codecov-action@v1 40 | 41 | lint: 42 | name: Lint 43 | runs-on: ubuntu-latest 44 | 45 | steps: 46 | - name: Checkout code 47 | uses: actions/checkout@v4 48 | 49 | - name: Setup Go 50 | uses: actions/setup-go@v5 51 | with: 52 | go-version: 1.24.x 53 | cache: false # managed by golangci-lint 54 | 55 | - uses: golangci/golangci-lint-action@v3 56 | name: Install golangci-lint 57 | with: 58 | version: latest 59 | args: --help # make lint will run the linter 60 | 61 | - run: make lint 62 | name: Lint 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | /vendor 3 | /.bench 4 | *.mem 5 | *.cpu 6 | *.test 7 | *.log 8 | *.out 9 | *.html 10 | *.coverprofile 11 | coverage.txt 12 | *.pprof 13 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | output: 2 | # Make output more digestible with quickfix in vim/emacs/etc. 3 | sort-results: true 4 | print-issued-lines: false 5 | 6 | linters: 7 | # We'll track the golangci-lint default linters manually 8 | # instead of letting them change without our control. 9 | disable-all: true 10 | enable: 11 | # golangci-lint defaults: 12 | - gosimple 13 | - govet 14 | - ineffassign 15 | - staticcheck 16 | - unused 17 | 18 | # Our own extras: 19 | - gofumpt 20 | - nolintlint # lints nolint directives 21 | - revive 22 | - errorlint 23 | 24 | # License header check 25 | - goheader 26 | 27 | linters-settings: 28 | govet: 29 | # These govet checks are disabled by default, but they're useful. 30 | enable: 31 | - niliness 32 | - reflectvaluecompare 33 | - sortslice 34 | - unusedwrite 35 | 36 | goheader: 37 | values: 38 | const: 39 | COMPANY: 'Uber Technologies, Inc.' 40 | regexp: 41 | YEAR_RANGE: '\d{4}(-\d{4})?' 42 | template: |- 43 | Copyright (c) {{ YEAR_RANGE }} {{ COMPANY }} 44 | 45 | Permission is hereby granted, free of charge, to any person obtaining a copy 46 | of this software and associated documentation files (the "Software"), to deal 47 | in the Software without restriction, including without limitation the rights 48 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 49 | copies of the Software, and to permit persons to whom the Software is 50 | furnished to do so, subject to the following conditions: 51 | 52 | The above copyright notice and this permission notice shall be included in 53 | all copies or substantial portions of the Software. 54 | 55 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 56 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 57 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 58 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 59 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 60 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 61 | THE SOFTWARE. 62 | 63 | issues: 64 | # Print all issues reported by all linters. 65 | max-issues-per-linter: 0 66 | max-same-issues: 0 67 | 68 | # Don't ignore some of the issues that golangci-lint considers okay. 69 | # This includes documenting all exported entities. 70 | exclude-use-default: false 71 | 72 | exclude-rules: 73 | # Don't warn on unused parameters. 74 | # Parameter names are useful; replacing them with '_' is undesirable. 75 | - linters: [revive] 76 | text: 'unused-parameter: parameter \S+ seems to be unused, consider removing or renaming it as _' 77 | 78 | # staticcheck already has smarter checks for empty blocks. 79 | # revive's empty-block linter has false positives. 80 | # For example, as of writing this, the following is not allowed. 81 | # for foo() { } 82 | - linters: [revive] 83 | text: 'empty-block: this block is empty, you can remove it' 84 | 85 | # It's okay if internal packages and examples in docs/ 86 | # don't have package comments. 87 | - linters: [revive] 88 | path: '.+/internal/.+|^internal/.+' 89 | text: 'should have a package comment' 90 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | - No changes yet. 9 | 10 | ## [1.19.0] - 2025-05-13 11 | 12 | ### Added 13 | - `BeforeCallback`s can be registered using `WithProviderBeforeCallback` and 14 | `WithDecoratorBeforeCallback` to hook custom callbacks into Dig 15 | to be run before constructors or decorators are run. 16 | 17 | ### Changed 18 | - Dot graph generation now results in much more clean and organized dot files. 19 | 20 | Thanks to @Groxx, @architagr, and @pgimalac for their contributions to this release. 21 | 22 | [1.19.0]: https://github.com/uber-go/dig/compare/v1.18.2...v1.19.0 23 | 24 | ## [1.18.2] - 2025-05-08 25 | ### Fixed 26 | - The exported Version string incorrectly containing "-dev". 27 | 28 | [1.18.2]: https://github.com/uber-go/dig/compare/v1.18.1...v1.18.2 29 | 30 | ## [1.18.1] - 2025-03-03 31 | ### Changed 32 | - Dot graph generation's internal implementation was changed so that 33 | Go linker can successfully perform dead code elimination for Go apps 34 | that use Dig. 35 | 36 | Thanks to @pgimalac for their contribution to this releaase. 37 | 38 | [1.18.1]: https://github.com/uber-go/dig/compare/v1.18.0...v1.18.1 39 | 40 | ## [1.18.0] - 2024-08-07 41 | ### Added 42 | - Child scope constructors are now visualized via `Visualize` 43 | - `CallbackInfo` now includes constructor/decorator run time. 44 | 45 | Thanks to @greeflas for their contribution to this release. 46 | 47 | [1.18.0]: https://github.com/uber-go/dig/compare/v1.17.1...v1.18.0 48 | 49 | ## [1.17.1] - 2023-10-19 50 | ### Added 51 | - Suggestions for value vs. pointer elements for slice and array types. 52 | 53 | ### Fixed 54 | - An issue where value group values were not getting decorated 55 | by decorators within the same module when using dig.Export(true). 56 | - A typo in docs. 57 | - An issue where false positives in cycle detection were occurring 58 | when providing to a child scope. 59 | 60 | Thanks to @paullen and @lcarilla for their contributions to this release. 61 | 62 | [1.17.1]: https://github.com/uber-go/dig/compare/v1.17.0...v1.17.1 63 | 64 | ## [1.17.0] - 2023-05-02 65 | ### Added 66 | - Allow using `dig.As` with `dig.Group`. 67 | - Add `FillInvokeInfo` Option and `InvokeInfo` struct to help 68 | extract the types requested by an `Invoke` statement. 69 | - To get visibility into constructor and decorator calls, introduce 70 | `WithProviderCallback` and `WithDecoratorCallback` Options to provide callback functions. 71 | 72 | [1.17.0]: https://github.com/uber-go/dig/compare/v1.16.1...v1.17.0 73 | 74 | ## [1.16.1] - 2023-01-10 75 | ### Fixed 76 | - A panic when `DryRun` was used with `Decorate`. 77 | 78 | [1.16.1]: https://github.com/uber-go/dig/compare/v1.16.0...v1.16.1 79 | 80 | ## [1.16.0] - 2023-01-03 81 | ### Added 82 | - Add `RecoverFromPanics` option, which provides panic-recovery mechanism for Container. 83 | - Add `Error` interface which enables distinguishing errors from Dig using standard `errors` 84 | package. 85 | 86 | Thanks to @mie998 for their contribution(s) to this release. 87 | 88 | [1.16.0]: https://github.com/uber-go/dig/compare/v1.15.0...v1.16.0 89 | 90 | ## [1.15.0] - 2022-08-02 91 | ### Added 92 | - Support for `soft` value groups, which specify a value group that only gets populated 93 | with values from already-executed constructors. 94 | 95 | ### Fixed 96 | - Fix an issue with invoke order affecting results provided by private provides 97 | 98 | Thanks to @hbdf for their contributions to this release. 99 | 100 | [1.15.0]: https://github.com/uber-go/dig/compare/v1.14.1...v1.15.0 101 | 102 | ## [1.14.1] - 2022-03-22 103 | ### Fixed 104 | - Fix an issue where a dependency for a decoration supplied by another decorator in the 105 | same scope is ignored. 106 | - Fix a panic when submitting a single value as a value group in `Scope.Decorate`. 107 | - Upon a provide error, make the error message contain the function named specified 108 | by LocationForPC Option. 109 | 110 | [1.14.1]: https://github.com/uber-go/dig/compare/v1.14.0...v1.14.1 111 | 112 | ## [1.14.0] - 2022-02-23 113 | ### Added 114 | - Introduce `dig.Scope` which creates a scoped dependency injection 115 | container to scope dependencies. 116 | - Introduce `Scope.Decorate` and `Container.Decorate` which allows a 117 | decorator to modify a dependency already provided in the dependency graph. 118 | - Add `FillDecorateInfo` Option and `DecorateInfo` struct which exposes 119 | information on what Dig was able to understand from the decorator provided 120 | with `Scope.Decorate` or `Container.Decorate`. 121 | 122 | ### Changed 123 | - The error message that appears when a cycle is detected in the dependency graph 124 | has been changed slightly. 125 | 126 | ### Fixed 127 | - A stack overflow bug that happens when cycles are introduced via self-pointing 128 | dependencies with DeferAcyclicVerification. 129 | 130 | [1.14.0]: https://github.com/uber-go/dig/compare/v1.13.0...v1.14.0 131 | 132 | ## [1.13.0] - 2021-09-21 133 | ### Added 134 | - Introduce `As` option which supports providing a type as interface(s) 135 | it implements to the container. 136 | - Add `LocationForPC` option which overrides the function inspection 137 | for a program counter address to a provided function info. 138 | 139 | [1.13.0]: https://github.com/uber-go/dig/compare/v1.12.0...v1.13.0 140 | 141 | ## [1.12.0] - 2021-07-29 142 | ### Added 143 | - Support for ProvideInfo and FillProvideInfo that allow the caller of 144 | `Provide` to get info about what dig understood from the constructor. 145 | 146 | [1.12.0]: https://github.com/uber-go/dig/compare/v1.11.0...v1.12.0 147 | 148 | ## [1.11.0] - 2021-06-09 149 | ### Added 150 | - Support unexported fields on `dig.In` structs with the 151 | `ignore-unexported:"true` struct tag. 152 | 153 | [1.11.0]: https://github.com/uber-go/dig/compare/v1.10.0...v1.11.0 154 | 155 | ## [1.10.0] - 2020-06-16 156 | ### Added 157 | - Introduce `DryRun` Option which, when set to true, disables invocation 158 | of functions supplied to `Provide` and `Invoke`. This option will be 159 | used to build no-op containers, for example for `fx.ValidateApp` method. 160 | 161 | [1.10.0]: https://github.com/uber-go/dig/compare/v1.9.0...v1.10.0 162 | 163 | ## [1.9.0] - 2020-03-31 164 | ### Added 165 | - GraphViz visualization of the graph now includes names of packages next to 166 | constructors. 167 | - Added a `flatten` modifier to group tags for slices to allow providing 168 | individual elements instead of the slice for a group value. See package 169 | doucmentation for more information. 170 | 171 | ### Changed 172 | - Drop library dependency on `golang.org/x/lint`. 173 | - Support printing multi-line error messages with `%+v`. 174 | 175 | [1.9.0]: https://github.com/uber-go/dig/compare/v1.8.0...v1.9.0 176 | 177 | ## [1.8.0] - 2019-11-14 178 | ### Changed 179 | - Migrated to Go modules. 180 | 181 | [1.8.0]: https://github.com/uber-go/dig/compare/v1.7.0...v1.8.0 182 | 183 | ## [1.7.0] - 2019-01-04 184 | ### Added 185 | - Added `Group` option for `Provide` to add value groups to the container without 186 | rewriting constructors. See package doucmentation for more information. 187 | 188 | [1.7.0]: https://github.com/uber-go/dig/compare/v1.6.0...v1.7.0 189 | 190 | ## [1.6.0] - 2018-11-06 191 | ### Changed 192 | - When an error graph is visualized, the graph is pruned so that the graph only 193 | contains failure nodes. 194 | - Container visualization is now oriented from right to left. 195 | 196 | [1.6.0]: https://github.com/uber-go/dig/compare/v1.5.1...v1.6.0 197 | 198 | ## [1.5.1] - 2018-11-01 199 | ### Fixed 200 | - Fixed a test that was causing Dig to be unusable with Go Modules. 201 | 202 | [1.5.1]: https://github.com/uber-go/dig/compare/v1.5.0...v1.5.1 203 | 204 | ## [1.5.0] - 2018-09-19 205 | ### Added 206 | - Added a `DeferAcyclicVerification` container option that defers graph cycle 207 | detection until the next Invoke. 208 | 209 | ### Changed 210 | - Improved cycle-detection performance by 50x in certain degenerative cases. 211 | 212 | [1.5.0]: https://github.com/uber-go/dig/compare/v1.4.0...v1.5.0 213 | 214 | ## [1.4.0] - 2018-08-16 215 | ### Added 216 | - Added `Visualize` function to visualize the state of the container in the 217 | GraphViz DOT format. This allows visualization of error types and the 218 | dependency relationships of types in the container. 219 | - Added `CanVisualizeError` function to determine if an error can be visualized 220 | in the graph. 221 | - Added `Name` option for `Provide` to add named values to the container 222 | without rewriting constructors. See package documentation for more 223 | information. 224 | 225 | ### Changed 226 | - `name:"..."` tags on nested Result Objects will now cause errors instead of 227 | being ignored. 228 | 229 | [1.4.0]: https://github.com/uber-go/dig/compare/v1.3.0...v1.4.0 230 | 231 | ## [1.3.0] - 2017-12-04 232 | ### Changed 233 | - Improved messages for errors thrown by Dig under a many scenarios to be more 234 | informative. 235 | 236 | [1.3.0]: https://github.com/uber-go/dig/compare/v1.2.0...v1.3.0 237 | 238 | ## [1.2.0] - 2017-11-07 239 | ### Added 240 | - `dig.In` and `dig.Out` now support value groups, making it possible to 241 | produce many values of the same type from different constructors. See package 242 | documentation for more information. 243 | 244 | [1.2.0]: https://github.com/uber-go/dig/compare/v1.1.0...v1.2.0 245 | 246 | ## [1.1.0] - 2017-09-15 247 | ### Added 248 | - Added the `dig.RootCause` function which allows retrieving the original 249 | constructor error that caused an `Invoke` failure. 250 | 251 | ### Changed 252 | - Errors from `Invoke` now attempt to hint to the user a presence of a similar 253 | type, for example a pointer to the requested type and vice versa. 254 | 255 | [1.1.0]: https://github.com/uber-go/dig/compare/v1.0.0...v1.1.0 256 | 257 | ## [1.0.0] - 2017-07-31 258 | 259 | First stable release: no breaking changes will be made in the 1.x series. 260 | 261 | ### Changed 262 | - `Provide` and `Invoke` will now fail if `dig.In` or `dig.Out` structs 263 | contain unexported fields. Previously these fields were ignored which often 264 | led to confusion. 265 | 266 | [1.0.0]: https://github.com/uber-go/dig/compare/v1.0.0-rc2...v1.0.0 267 | 268 | ## [1.0.0-rc2] - 2017-07-21 269 | ### Added 270 | - Exported `dig.IsIn` and `dig.IsOut` so that consuming libraries can check if 271 | a params or return struct embeds the `dig.In` and `dig.Out` types, respectively. 272 | 273 | ### Changed 274 | - Added variadic options to all public APIS so that new functionality can be 275 | introduced post v1.0.0 without introducing breaking changes. 276 | - Functions with variadic arguments can now be passed to `dig.Provide` and 277 | `dig.Invoke`. Previously this caused an error, whereas now the args will be ignored. 278 | 279 | [1.0.0-rc2]: https://github.com/uber-go/dig/compare/v1.0.0-rc1...v1.0.0-rc2 280 | 281 | ## [1.0.0-rc1] - 2017-06-21 282 | 283 | First release candidate. 284 | 285 | [1.0.0-rc1]: https://github.com/uber-go/dig/compare/v0.5.0...v1.0.0-rc1 286 | 287 | 288 | ## [0.5.0] - 2017-06-19 289 | ### Added 290 | - `dig.In` and `dig.Out` now support named instances, i.e.: 291 | 292 | ```go 293 | type param struct { 294 | dig.In 295 | 296 | DB1 DB.Connection `name:"primary"` 297 | DB2 DB.Connection `name:"secondary"` 298 | } 299 | ``` 300 | 301 | ### Fixed 302 | - Structs compatible with `dig.In` and `dig.Out` may now be generated using 303 | `reflect.StructOf`. 304 | 305 | [0.5.0]: https://github.com/uber-go/dig/compare/v0.4.0...v0.5.0 306 | 307 | ## [0.4.0] - 2017-06-12 308 | ### Added 309 | - Add `dig.In` embeddable type for advanced use-cases of specifying dependencies. 310 | - Add `dig.Out` embeddable type for advanced use-cases of constructors 311 | inserting types in the container. 312 | - Add support for optional parameters through `optional:"true"` tag on `dig.In` objects. 313 | - Add support for value types and many built-ins (maps, slices, channels). 314 | 315 | ### Changed 316 | - **[Breaking]** Restrict the API surface to only `Provide` and `Invoke`. 317 | - **[Breaking]** Update `Provide` method to accept variadic arguments. 318 | 319 | ### Removed 320 | - **[Breaking]** Remove `Must*` funcs to greatly reduce API surface area. 321 | - Providing constructors with common returned types results in an error. 322 | 323 | [0.4.0]: https://github.com/uber-go/dig/compare/v0.3...v0.4.0 324 | 325 | ## [0.3] - 2017-05-02 326 | ### Added 327 | - Add functionality to `Provide` to support constructor with `n` return 328 | objects to be resolved into the `dig.Graph` 329 | - Add `Invoke` function to invoke provided function and insert return 330 | objects into the `dig.Graph` 331 | 332 | ### Changed 333 | - Rename `RegisterAll` and `MustRegisterAll` to `ProvideAll` and 334 | `MustProvideAll`. 335 | 336 | [0.3]: https://github.com/uber-go/dig/compare/v0.2...v0.3 337 | 338 | ## [0.2] - 2017-03-27 339 | ### Changed 340 | - Rename `Register` to `Provide` for clarity and to recude clash with other 341 | Register functions. 342 | - Rename `dig.Graph` to `dig.Container`. 343 | 344 | ### Removed 345 | - Remove the package-level functions and the `DefaultGraph`. 346 | 347 | [0.2]: https://github.com/uber-go/dig/compare/v0.1...v0.2 348 | 349 | ## 0.1 - 2017-03-23 350 | 351 | Initial release. 352 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-2018 Uber Technologies, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Directory containing the Makefile. 2 | PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) 3 | 4 | export GOBIN ?= $(PROJECT_ROOT)/bin 5 | export PATH := $(GOBIN):$(PATH) 6 | 7 | BENCH_FLAGS ?= -cpuprofile=cpu.pprof -memprofile=mem.pprof -benchmem 8 | 9 | GO_FILES = $(shell \ 10 | find . '(' -path '*/.*' -o -path './vendor' ')' -prune \ 11 | -o -name '*.go' -print | cut -b3-) 12 | 13 | .PHONY: all 14 | all: build lint test 15 | 16 | .PHONY: build 17 | build: 18 | go build ./... 19 | 20 | .PHONY: lint 21 | lint: golangci-lint tidy-lint 22 | 23 | .PHONY: test 24 | test: 25 | go test -race ./... 26 | 27 | .PHONY: cover 28 | cover: 29 | go test -race -coverprofile=cover.out -coverpkg=./... ./... 30 | go tool cover -html=cover.out -o cover.html 31 | 32 | .PHONY: bench 33 | BENCH ?= . 34 | bench: 35 | go list ./... | xargs -n1 go test -bench=$(BENCH) -run="^$$" $(BENCH_FLAGS) 36 | 37 | .PHONY: tidy 38 | tidy: 39 | go mod tidy 40 | 41 | .PHONY: golangci-lint 42 | golangci-lint: 43 | golangci-lint run 44 | 45 | .PHONY: tidy-lint 46 | tidy-lint: 47 | go mod tidy 48 | git diff --exit-code -- go.mod go.sum 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :hammer_and_pick: dig [![GoDoc][doc-img]][doc] [![GitHub release][release-img]][release] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![Go Report Card][report-card-img]][report-card] 2 | 3 | A reflection based dependency injection toolkit for Go. 4 | 5 | ### Good for: 6 | 7 | * Powering an application framework, e.g. [Fx](https://github.com/uber-go/fx). 8 | * Resolving the object graph during process startup. 9 | 10 | ### Bad for: 11 | 12 | * Using in place of an application framework, e.g. [Fx](https://github.com/uber-go/fx). 13 | * Resolving dependencies after the process has already started. 14 | * Exposing to user-land code as a [Service Locator](https://martinfowler.com/articles/injection.html#UsingAServiceLocator). 15 | 16 | ## Installation 17 | 18 | We recommend consuming [SemVer](http://semver.org/) major version `1` using 19 | your dependency manager of choice. 20 | 21 | ``` 22 | $ glide get 'go.uber.org/dig#^1' 23 | $ dep ensure -add "go.uber.org/dig@v1" 24 | $ go get 'go.uber.org/dig@v1' 25 | ``` 26 | 27 | ## Stability 28 | 29 | This library is `v1` and follows [SemVer](http://semver.org/) strictly. 30 | 31 | No breaking changes will be made to exported APIs before `v2.0.0`. 32 | 33 | [doc-img]: http://img.shields.io/badge/GoDoc-Reference-blue.svg 34 | [doc]: https://godoc.org/go.uber.org/dig 35 | 36 | [release-img]: https://img.shields.io/github/release/uber-go/dig.svg 37 | [release]: https://github.com/uber-go/dig/releases 38 | 39 | [ci-img]: https://github.com/uber-go/dig/actions/workflows/go.yml/badge.svg 40 | [ci]: https://github.com/uber-go/dig/actions/workflows/go.yml 41 | 42 | [cov-img]: https://codecov.io/gh/uber-go/dig/branch/master/graph/badge.svg 43 | [cov]: https://codecov.io/gh/uber-go/dig/branch/master 44 | 45 | [report-card-img]: https://goreportcard.com/badge/github.com/uber-go/dig 46 | [report-card]: https://goreportcard.com/report/github.com/uber-go/dig 47 | 48 | ## Stargazers over time 49 | 50 | [![Stargazers over time](https://starchart.cc/uber-go/dig.svg)](https://starchart.cc/uber-go/dig) 51 | 52 | -------------------------------------------------------------------------------- /before_callback.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | // BeforeCallbackInfo contains information about a provided function or decorator 24 | // called by Dig, and is passed to a [BeforeCallback] registered with 25 | // [WithProviderBeforeCallback] or [WithDecoratorBeforeCallback]. 26 | type BeforeCallbackInfo struct { 27 | // Name is the name of the function in the format: 28 | // . 29 | Name string 30 | } 31 | 32 | // BeforeCallback is a function that can be registered with a provided function 33 | // using [WithProviderBeforeCallback] or decorator using [WithDecoratorBeforeCallback] 34 | // to cause it to be called before the provided function or decorator is run. 35 | type BeforeCallback func(bci BeforeCallbackInfo) 36 | 37 | // WithProviderBeforeCallback returns a [ProvideOption] which has Dig call 38 | // the passed in [BeforeCallback] before the corresponding constructor begins running. 39 | // 40 | // For example, the following prints a message 41 | // before "myConstructor" is called: 42 | // 43 | // c := dig.New() 44 | // myCallback := func(bci BeforeCallbackInfo) { 45 | // fmt.Printf("%q started", bci.Name) 46 | // } 47 | // c.Provide(myConstructor, WithProviderBeforeCallback(myCallback)), 48 | // 49 | // BeforeCallbacks can also be specified for Decorators with [WithDecoratorBeforeCallback]. 50 | // 51 | // See [BeforeCallbackInfo] for more info on the information passed to the [BeforeCallback]. 52 | func WithProviderBeforeCallback(callback BeforeCallback) ProvideOption { 53 | return withBeforeCallbackOption{ 54 | callback: callback, 55 | } 56 | } 57 | 58 | // WithDecoratorBeforeCallback returns a [DecorateOption] which has Dig call 59 | // the passed in [BeforeCallback] before the corresponding decorator begins running. 60 | // 61 | // For example, the following prints a message 62 | // before "myDecorator" is called: 63 | // 64 | // c := dig.New() 65 | // myCallback := func(bci BeforeCallbackInfo) { 66 | // fmt.Printf("%q started", bci.Name) 67 | // } 68 | // c.Decorate(myDecorator, WithDecoratorBeforeCallback(myCallback)), 69 | // 70 | // BeforeCallbacks can also be specified for Constructors with [WithProviderBeforeCallback]. 71 | // 72 | // See [BeforeCallbackInfo] for more info on the information passed to the [BeforeCallback]. 73 | func WithDecoratorBeforeCallback(callback BeforeCallback) DecorateOption { 74 | return withBeforeCallbackOption{ 75 | callback: callback, 76 | } 77 | } 78 | 79 | type withBeforeCallbackOption struct { 80 | callback BeforeCallback 81 | } 82 | 83 | var ( 84 | _ ProvideOption = withBeforeCallbackOption{} 85 | _ DecorateOption = withBeforeCallbackOption{} 86 | ) 87 | 88 | func (o withBeforeCallbackOption) applyProvideOption(po *provideOptions) { 89 | po.BeforeCallback = o.callback 90 | } 91 | 92 | func (o withBeforeCallbackOption) apply(do *decorateOptions) { 93 | do.BeforeCallback = o.callback 94 | } 95 | -------------------------------------------------------------------------------- /callback.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import "time" 24 | 25 | // CallbackInfo contains information about a provided function or decorator 26 | // called by Dig, and is passed to a [Callback] registered with 27 | // [WithProviderCallback] or [WithDecoratorCallback]. 28 | type CallbackInfo struct { 29 | // Name is the name of the function in the format: 30 | // . 31 | Name string 32 | 33 | // Error contains the error returned by the [Callback]'s associated 34 | // function, if any. When used in conjunction with [RecoverFromPanics], 35 | // this will be set to a [PanicError] when the function panics. 36 | Error error 37 | 38 | // Runtime contains the duration it took for the associated 39 | // function to run. 40 | Runtime time.Duration 41 | } 42 | 43 | // Callback is a function that can be registered with a provided function 44 | // or decorator with [WithProviderCallback] or decorator with [WithDecoratorCallback] 45 | // to cause it to be called after the provided function or decorator is run. 46 | type Callback func(CallbackInfo) 47 | 48 | // WithProviderCallback returns a [ProvideOption] which has Dig call 49 | // the passed in [Callback] after the corresponding constructor finishes running. 50 | // 51 | // For example, the following prints a completion message 52 | // after "myConstructor" finishes, including the error if any: 53 | // 54 | // c := dig.New() 55 | // myCallback := func(ci CallbackInfo) { 56 | // var errorAdd string 57 | // if ci.Error != nil { 58 | // errorAdd = fmt.Sprintf("with error: %v", ci.Error) 59 | // } 60 | // fmt.Printf("%q finished%v", ci.Name, errorAdd) 61 | // } 62 | // c.Provide(myConstructor, WithProviderCallback(myCallback)), 63 | // 64 | // Callbacks can also be specified for Decorators with [WithDecoratorCallback]. 65 | // 66 | // See [CallbackInfo] for more info on the information passed to the [Callback]. 67 | func WithProviderCallback(callback Callback) ProvideOption { 68 | return withCallbackOption{ 69 | callback: callback, 70 | } 71 | } 72 | 73 | // WithDecoratorCallback returns a [DecorateOption] which has Dig call 74 | // the passed in [Callback] after the corresponding decorator finishes running. 75 | // 76 | // For example, the following prints a completion message 77 | // after "myDecorator" finishes, including the error if any: 78 | // 79 | // c := dig.New() 80 | // myCallback := func(ci CallbackInfo) { 81 | // var errorAdd string 82 | // if ci.Error != nil { 83 | // errorAdd = fmt.Sprintf("with error: %v", ci.Error) 84 | // } 85 | // fmt.Printf("%q finished%v", ci.Name, errorAdd) 86 | // } 87 | // c.Decorate(myDecorator, WithDecoratorCallback(myCallback)), 88 | // 89 | // Callbacks can also be specified for Constructors with [WithProviderCallback]. 90 | // 91 | // See [CallbackInfo] for more info on the information passed to the [Callback]. 92 | func WithDecoratorCallback(callback Callback) DecorateOption { 93 | return withCallbackOption{ 94 | callback: callback, 95 | } 96 | } 97 | 98 | type withCallbackOption struct { 99 | callback Callback 100 | } 101 | 102 | var ( 103 | _ ProvideOption = withCallbackOption{} 104 | _ DecorateOption = withCallbackOption{} 105 | ) 106 | 107 | func (o withCallbackOption) applyProvideOption(po *provideOptions) { 108 | po.Callback = o.callback 109 | } 110 | 111 | func (o withCallbackOption) apply(do *decorateOptions) { 112 | do.Callback = o.callback 113 | } 114 | -------------------------------------------------------------------------------- /constructor.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "fmt" 25 | "reflect" 26 | 27 | "go.uber.org/dig/internal/digerror" 28 | "go.uber.org/dig/internal/digreflect" 29 | "go.uber.org/dig/internal/dot" 30 | ) 31 | 32 | // constructorNode is a node in the dependency graph that represents 33 | // a constructor provided by the user. 34 | // 35 | // constructorNodes can produce zero or more values that they store into the container. 36 | // For the Provide path, we verify that constructorNodes produce at least one value, 37 | // otherwise the function will never be called. 38 | type constructorNode struct { 39 | ctor interface{} 40 | ctype reflect.Type 41 | 42 | // Location where this function was defined. 43 | location *digreflect.Func 44 | 45 | // id uniquely identifies the constructor that produces a node. 46 | id dot.CtorID 47 | 48 | // Whether the constructor owned by this node was already called. 49 | called bool 50 | 51 | // Type information about constructor parameters. 52 | paramList paramList 53 | 54 | // Type information about constructor results. 55 | resultList resultList 56 | 57 | // Order of this node in each Scopes' graphHolders. 58 | orders map[*Scope]int 59 | 60 | // Scope this node is part of. 61 | s *Scope 62 | 63 | // Scope this node was originally provided to. 64 | // This is different from s if and only if the constructor was Provided with ExportOption. 65 | origS *Scope 66 | 67 | // Callback for this provided function, if there is one. 68 | callback Callback 69 | 70 | // BeforeCallback for this provided function, if there is one. 71 | beforeCallback BeforeCallback 72 | } 73 | 74 | type constructorOptions struct { 75 | // If specified, all values produced by this constructor have the provided name 76 | // belong to the specified value group or implement any of the interfaces. 77 | ResultName string 78 | ResultGroup string 79 | ResultAs []interface{} 80 | Location *digreflect.Func 81 | Callback Callback 82 | BeforeCallback BeforeCallback 83 | } 84 | 85 | func newConstructorNode(ctor interface{}, s *Scope, origS *Scope, opts constructorOptions) (*constructorNode, error) { 86 | cval := reflect.ValueOf(ctor) 87 | ctype := cval.Type() 88 | cptr := cval.Pointer() 89 | 90 | params, err := newParamList(ctype, s) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | results, err := newResultList( 96 | ctype, 97 | resultOptions{ 98 | Name: opts.ResultName, 99 | Group: opts.ResultGroup, 100 | As: opts.ResultAs, 101 | }, 102 | ) 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | location := opts.Location 108 | if location == nil { 109 | location = digreflect.InspectFunc(ctor) 110 | } 111 | 112 | n := &constructorNode{ 113 | ctor: ctor, 114 | ctype: ctype, 115 | location: location, 116 | id: dot.CtorID(cptr), 117 | paramList: params, 118 | resultList: results, 119 | orders: make(map[*Scope]int), 120 | s: s, 121 | origS: origS, 122 | callback: opts.Callback, 123 | beforeCallback: opts.BeforeCallback, 124 | } 125 | s.newGraphNode(n, n.orders) 126 | return n, nil 127 | } 128 | 129 | func (n *constructorNode) Location() *digreflect.Func { return n.location } 130 | func (n *constructorNode) ParamList() paramList { return n.paramList } 131 | func (n *constructorNode) ResultList() resultList { return n.resultList } 132 | func (n *constructorNode) ID() dot.CtorID { return n.id } 133 | func (n *constructorNode) CType() reflect.Type { return n.ctype } 134 | func (n *constructorNode) Order(s *Scope) int { return n.orders[s] } 135 | func (n *constructorNode) OrigScope() *Scope { return n.origS } 136 | 137 | // CopyOrder copies the order for the given parent scope to the given child scope. 138 | func (n *constructorNode) CopyOrder(parent, child *Scope) { 139 | n.orders[child] = n.orders[parent] 140 | } 141 | 142 | func (n *constructorNode) String() string { 143 | return fmt.Sprintf("deps: %v, ctor: %v", n.paramList, n.ctype) 144 | } 145 | 146 | // Call calls this constructor if it hasn't already been called and 147 | // injects any values produced by it into the provided container. 148 | func (n *constructorNode) Call(c containerStore) (err error) { 149 | if n.called { 150 | return nil 151 | } 152 | 153 | if err := shallowCheckDependencies(c, n.paramList); err != nil { 154 | return errMissingDependencies{ 155 | Func: n.location, 156 | Reason: err, 157 | } 158 | } 159 | 160 | args, err := n.paramList.BuildList(c) 161 | if err != nil { 162 | return errArgumentsFailed{ 163 | Func: n.location, 164 | Reason: err, 165 | } 166 | } 167 | 168 | if n.beforeCallback != nil { 169 | n.beforeCallback(BeforeCallbackInfo{ 170 | Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name), 171 | }) 172 | } 173 | 174 | if n.callback != nil { 175 | start := c.clock().Now() 176 | // Wrap in separate func to include PanicErrors 177 | defer func() { 178 | n.callback(CallbackInfo{ 179 | Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name), 180 | Error: err, 181 | Runtime: c.clock().Since(start), 182 | }) 183 | }() 184 | } 185 | 186 | if n.s.recoverFromPanics { 187 | defer func() { 188 | if p := recover(); p != nil { 189 | err = PanicError{ 190 | fn: n.location, 191 | Panic: p, 192 | } 193 | } 194 | }() 195 | } 196 | 197 | receiver := newStagingContainerWriter() 198 | results := c.invoker()(reflect.ValueOf(n.ctor), args) 199 | if err = n.resultList.ExtractList(receiver, false /* decorating */, results); err != nil { 200 | return errConstructorFailed{Func: n.location, Reason: err} 201 | } 202 | 203 | // Commit the result to the original container that this constructor 204 | // was supplied to. The provided constructor is only used for a view of 205 | // the rest of the graph to instantiate the dependencies of this 206 | // container. 207 | receiver.Commit(n.s) 208 | n.called = true 209 | return nil 210 | } 211 | 212 | // stagingContainerWriter is a containerWriter that records the changes that 213 | // would be made to a containerWriter and defers them until Commit is called. 214 | type stagingContainerWriter struct { 215 | values map[key]reflect.Value 216 | groups map[key][]reflect.Value 217 | } 218 | 219 | var _ containerWriter = (*stagingContainerWriter)(nil) 220 | 221 | func newStagingContainerWriter() *stagingContainerWriter { 222 | return &stagingContainerWriter{ 223 | values: make(map[key]reflect.Value), 224 | groups: make(map[key][]reflect.Value), 225 | } 226 | } 227 | 228 | func (sr *stagingContainerWriter) setValue(name string, t reflect.Type, v reflect.Value) { 229 | sr.values[key{t: t, name: name}] = v 230 | } 231 | 232 | func (sr *stagingContainerWriter) setDecoratedValue(_ string, _ reflect.Type, _ reflect.Value) { 233 | digerror.BugPanicf("stagingContainerWriter.setDecoratedValue must never be called") 234 | } 235 | 236 | func (sr *stagingContainerWriter) submitGroupedValue(group string, t reflect.Type, v reflect.Value) { 237 | k := key{t: t, group: group} 238 | sr.groups[k] = append(sr.groups[k], v) 239 | } 240 | 241 | func (sr *stagingContainerWriter) submitDecoratedGroupedValue(_ string, _ reflect.Type, _ reflect.Value) { 242 | digerror.BugPanicf("stagingContainerWriter.submitDecoratedGroupedValue must never be called") 243 | } 244 | 245 | // Commit commits the received results to the provided containerWriter. 246 | func (sr *stagingContainerWriter) Commit(cw containerWriter) { 247 | for k, v := range sr.values { 248 | cw.setValue(k.name, k.t, v) 249 | } 250 | 251 | for k, vs := range sr.groups { 252 | for _, v := range vs { 253 | cw.submitGroupedValue(k.group, k.t, v) 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /constructor_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | "github.com/stretchr/testify/require" 28 | "go.uber.org/dig/internal/digreflect" 29 | ) 30 | 31 | func TestNewDotCtor(t *testing.T) { 32 | type t1 struct{} 33 | type t2 struct{} 34 | 35 | s := newScope() 36 | n, err := newConstructorNode(func(A t1) t2 { return t2{} }, s, s, constructorOptions{}) 37 | require.NoError(t, err) 38 | 39 | n.location = &digreflect.Func{ 40 | Name: "function1", 41 | Package: "pkg1", 42 | File: "file1", 43 | Line: 24534, 44 | } 45 | 46 | ctor := newDotCtor(n) 47 | assert.Equal(t, n.id, ctor.ID) 48 | assert.Equal(t, "function1", ctor.Name) 49 | assert.Equal(t, "pkg1", ctor.Package) 50 | assert.Equal(t, "file1", ctor.File) 51 | assert.Equal(t, 24534, ctor.Line) 52 | } 53 | 54 | func TestNodeAlreadyCalled(t *testing.T) { 55 | type type1 struct{} 56 | f := func() type1 { return type1{} } 57 | 58 | s := newScope() 59 | n, err := newConstructorNode(f, s, s, constructorOptions{}) 60 | require.NoError(t, err, "failed to build node") 61 | require.False(t, n.called, "node must not have been called") 62 | 63 | c := New() 64 | require.NoError(t, n.Call(c.scope), "invoke failed") 65 | require.True(t, n.called, "node must be called") 66 | require.NoError(t, n.Call(c.scope), "calling again should be okay") 67 | } 68 | -------------------------------------------------------------------------------- /container.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "fmt" 25 | "math/rand" 26 | "reflect" 27 | 28 | "go.uber.org/dig/internal/digclock" 29 | "go.uber.org/dig/internal/dot" 30 | ) 31 | 32 | const ( 33 | _optionalTag = "optional" 34 | _nameTag = "name" 35 | _ignoreUnexportedTag = "ignore-unexported" 36 | ) 37 | 38 | // Unique identification of an object in the graph. 39 | type key struct { 40 | t reflect.Type 41 | 42 | // Only one of name or group will be set. 43 | name string 44 | group string 45 | } 46 | 47 | func (k key) String() string { 48 | if k.name != "" { 49 | return fmt.Sprintf("%v[name=%q]", k.t, k.name) 50 | } 51 | if k.group != "" { 52 | return fmt.Sprintf("%v[group=%q]", k.t, k.group) 53 | } 54 | return k.t.String() 55 | } 56 | 57 | // Option configures a Container. 58 | type Option interface { 59 | applyOption(*Container) 60 | } 61 | 62 | // Container is a directed acyclic graph of types and their dependencies. 63 | // A Container is the root Scope that represents the top-level scoped 64 | // directed acyclic graph of the dependencies. 65 | type Container struct { 66 | // this is the "root" Scope that represents the 67 | // root of the scope tree. 68 | scope *Scope 69 | } 70 | 71 | // containerWriter provides write access to the Container's underlying data 72 | // store. 73 | type containerWriter interface { 74 | // setValue sets the value with the given name and type in the container. 75 | // If a value with the same name and type already exists, it will be 76 | // overwritten. 77 | setValue(name string, t reflect.Type, v reflect.Value) 78 | 79 | // setDecoratedValue sets a decorated value with the given name and type 80 | // in the container. If a decorated value with the same name and type already 81 | // exists, it will be overwritten. 82 | setDecoratedValue(name string, t reflect.Type, v reflect.Value) 83 | 84 | // submitGroupedValue submits a value to the value group with the provided 85 | // name. 86 | submitGroupedValue(name string, t reflect.Type, v reflect.Value) 87 | 88 | // submitDecoratedGroupedValue submits a decorated value to the value group 89 | // with the provided name. 90 | submitDecoratedGroupedValue(name string, t reflect.Type, v reflect.Value) 91 | } 92 | 93 | // containerStore provides access to the Container's underlying data store. 94 | type containerStore interface { 95 | containerWriter 96 | 97 | // Adds a new graph node to the Container 98 | newGraphNode(w interface{}, orders map[*Scope]int) 99 | 100 | // Returns a slice containing all known types. 101 | knownTypes() []reflect.Type 102 | 103 | // Retrieves the value with the provided name and type, if any. 104 | getValue(name string, t reflect.Type) (v reflect.Value, ok bool) 105 | 106 | // Retrieves a decorated value with the provided name and type, if any. 107 | getDecoratedValue(name string, t reflect.Type) (v reflect.Value, ok bool) 108 | 109 | // Retrieves all values for the provided group and type. 110 | // 111 | // The order in which the values are returned is undefined. 112 | getValueGroup(name string, t reflect.Type) []reflect.Value 113 | 114 | // Retrieves all decorated values for the provided group and type, if any. 115 | getDecoratedValueGroup(name string, t reflect.Type) (reflect.Value, bool) 116 | 117 | // Returns the providers that can produce a value with the given name and 118 | // type. 119 | getValueProviders(name string, t reflect.Type) []provider 120 | 121 | // Returns the providers that can produce values for the given group and 122 | // type. 123 | getGroupProviders(name string, t reflect.Type) []provider 124 | 125 | // Returns the providers that can produce a value with the given name and 126 | // type across all the Scopes that are in effect of this containerStore. 127 | getAllValueProviders(name string, t reflect.Type) []provider 128 | 129 | // Returns the decorator that can decorate values for the given name and 130 | // type. 131 | getValueDecorator(name string, t reflect.Type) (decorator, bool) 132 | 133 | // Reutrns the decorator that can decorate values for the given group and 134 | // type. 135 | getGroupDecorator(name string, t reflect.Type) (decorator, bool) 136 | 137 | // Reports a list of stores (starting at this store) up to the root 138 | // store. 139 | storesToRoot() []containerStore 140 | 141 | createGraph() *dot.Graph 142 | 143 | // Returns invokerFn function to use when calling arguments. 144 | invoker() invokerFn 145 | 146 | // Returns a clock to use 147 | clock() digclock.Clock 148 | } 149 | 150 | // New constructs a Container. 151 | func New(opts ...Option) *Container { 152 | s := newScope() 153 | c := &Container{scope: s} 154 | 155 | for _, opt := range opts { 156 | opt.applyOption(c) 157 | } 158 | return c 159 | } 160 | 161 | // DeferAcyclicVerification is an Option to override the default behavior 162 | // of container.Provide, deferring the dependency graph validation to no longer 163 | // run after each call to container.Provide. The container will instead verify 164 | // the graph on first `Invoke`. 165 | // 166 | // Applications adding providers to a container in a tight loop may experience 167 | // performance improvements by initializing the container with this option. 168 | func DeferAcyclicVerification() Option { 169 | return deferAcyclicVerificationOption{} 170 | } 171 | 172 | type deferAcyclicVerificationOption struct{} 173 | 174 | func (deferAcyclicVerificationOption) String() string { 175 | return "DeferAcyclicVerification()" 176 | } 177 | 178 | func (deferAcyclicVerificationOption) applyOption(c *Container) { 179 | c.scope.deferAcyclicVerification = true 180 | } 181 | 182 | // RecoverFromPanics is an [Option] to recover from panics that occur while 183 | // running functions given to the container. When set, recovered panics 184 | // will be placed into a [PanicError], and returned at the invoke callsite. 185 | // See [PanicError] for an example on how to handle panics with this option 186 | // enabled, and distinguish them from errors. 187 | func RecoverFromPanics() Option { 188 | return recoverFromPanicsOption{} 189 | } 190 | 191 | type recoverFromPanicsOption struct{} 192 | 193 | func (recoverFromPanicsOption) String() string { 194 | return "RecoverFromPanics()" 195 | } 196 | 197 | func (recoverFromPanicsOption) applyOption(c *Container) { 198 | c.scope.recoverFromPanics = true 199 | } 200 | 201 | // Changes the source of randomness for the container. 202 | // 203 | // This will help provide determinism during tests. 204 | func setRand(r *rand.Rand) Option { 205 | return setRandOption{r: r} 206 | } 207 | 208 | type setRandOption struct{ r *rand.Rand } 209 | 210 | func (o setRandOption) String() string { 211 | return fmt.Sprintf("setRand(%p)", o.r) 212 | } 213 | 214 | func (o setRandOption) applyOption(c *Container) { 215 | c.scope.rand = o.r 216 | } 217 | 218 | // Changes the source of time for the container. 219 | func setClock(c digclock.Clock) Option { 220 | return setClockOption{c: c} 221 | } 222 | 223 | type setClockOption struct{ c digclock.Clock } 224 | 225 | func (o setClockOption) String() string { 226 | return fmt.Sprintf("setClock(%v)", o.c) 227 | } 228 | 229 | func (o setClockOption) applyOption(c *Container) { 230 | c.scope.clockSrc = o.c 231 | } 232 | 233 | // DryRun is an Option which, when set to true, disables invocation of functions supplied to 234 | // Provide and Invoke. Use this to build no-op containers. 235 | func DryRun(dry bool) Option { 236 | return dryRunOption(dry) 237 | } 238 | 239 | type dryRunOption bool 240 | 241 | func (o dryRunOption) String() string { 242 | return fmt.Sprintf("DryRun(%v)", bool(o)) 243 | } 244 | 245 | func (o dryRunOption) applyOption(c *Container) { 246 | if o { 247 | c.scope.invokerFn = dryInvoker 248 | } else { 249 | c.scope.invokerFn = defaultInvoker 250 | } 251 | } 252 | 253 | // invokerFn specifies how the container calls user-supplied functions. 254 | type invokerFn func(fn reflect.Value, args []reflect.Value) (results []reflect.Value) 255 | 256 | func defaultInvoker(fn reflect.Value, args []reflect.Value) []reflect.Value { 257 | return fn.Call(args) 258 | } 259 | 260 | // Generates zero values for results without calling the supplied function. 261 | func dryInvoker(fn reflect.Value, _ []reflect.Value) []reflect.Value { 262 | ft := fn.Type() 263 | results := make([]reflect.Value, ft.NumOut()) 264 | for i := 0; i < ft.NumOut(); i++ { 265 | results[i] = reflect.Zero(fn.Type().Out(i)) 266 | } 267 | 268 | return results 269 | } 270 | 271 | // String representation of the entire Container 272 | func (c *Container) String() string { 273 | return c.scope.String() 274 | } 275 | 276 | // Scope creates a child scope of the Container with the given name. 277 | func (c *Container) Scope(name string, opts ...ScopeOption) *Scope { 278 | return c.scope.Scope(name, opts...) 279 | } 280 | 281 | type byTypeName []reflect.Type 282 | 283 | func (bs byTypeName) Len() int { 284 | return len(bs) 285 | } 286 | 287 | func (bs byTypeName) Less(i int, j int) bool { 288 | return fmt.Sprint(bs[i]) < fmt.Sprint(bs[j]) 289 | } 290 | 291 | func (bs byTypeName) Swap(i int, j int) { 292 | bs[i], bs[j] = bs[j], bs[i] 293 | } 294 | 295 | func shuffledCopy(rand *rand.Rand, items []reflect.Value) []reflect.Value { 296 | newItems := make([]reflect.Value, len(items)) 297 | for i, j := range rand.Perm(len(items)) { 298 | newItems[i] = items[j] 299 | } 300 | return newItems 301 | } 302 | -------------------------------------------------------------------------------- /container_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "fmt" 25 | "math/rand" 26 | "testing" 27 | 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | func TestOptionStrings(t *testing.T) { 32 | t.Parallel() 33 | 34 | t.Run("DeferAcyclicVerification", func(t *testing.T) { 35 | t.Parallel() 36 | 37 | assert.Equal(t, "DeferAcyclicVerification()", fmt.Sprint(DeferAcyclicVerification())) 38 | }) 39 | 40 | t.Run("setRand", func(t *testing.T) { 41 | t.Parallel() 42 | 43 | t.Run("nil", func(t *testing.T) { 44 | t.Parallel() 45 | 46 | assert.Equal(t, "setRand(0x0)", fmt.Sprint(setRand(nil))) 47 | }) 48 | 49 | t.Run("non nil", func(t *testing.T) { 50 | t.Parallel() 51 | 52 | opt := setRand(rand.New(rand.NewSource(42))) 53 | assert.NotEqual(t, "setRand(0x0)", fmt.Sprint(opt)) 54 | assert.Contains(t, fmt.Sprint(opt), "setRand(0x") 55 | }) 56 | }) 57 | 58 | t.Run("DryRun", func(t *testing.T) { 59 | t.Parallel() 60 | 61 | assert.Equal(t, "DryRun(true)", fmt.Sprint(DryRun(true))) 62 | assert.Equal(t, "DryRun(false)", fmt.Sprint(DryRun(false))) 63 | }) 64 | 65 | t.Run("RecoverFromPanics()", func(t *testing.T) { 66 | t.Parallel() 67 | 68 | assert.Equal(t, "RecoverFromPanics()", fmt.Sprint(RecoverFromPanics())) 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /cycle_error.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "bytes" 25 | "errors" 26 | "fmt" 27 | "io" 28 | 29 | "go.uber.org/dig/internal/digreflect" 30 | ) 31 | 32 | type cycleErrPathEntry struct { 33 | Key key 34 | Func *digreflect.Func 35 | } 36 | 37 | type errCycleDetected struct { 38 | Path []cycleErrPathEntry 39 | scope *Scope 40 | } 41 | 42 | var _ digError = errCycleDetected{} 43 | 44 | func (e errCycleDetected) Error() string { 45 | // We get something like, 46 | // 47 | // [scope "foo"] 48 | // func(*bar) *foo provided by "path/to/package".NewFoo (path/to/file.go:42) 49 | // depends on func(*baz) *bar provided by "another/package".NewBar (somefile.go:1) 50 | // depends on func(*foo) baz provided by "somepackage".NewBar (anotherfile.go:2) 51 | // depends on func(*bar) *foo provided by "path/to/package".NewFoo (path/to/file.go:42) 52 | // 53 | b := new(bytes.Buffer) 54 | 55 | if name := e.scope.name; len(name) > 0 { 56 | fmt.Fprintf(b, "[scope %q]\n", name) 57 | } 58 | for i, entry := range e.Path { 59 | if i > 0 { 60 | b.WriteString("\n\tdepends on ") 61 | } 62 | fmt.Fprintf(b, "%v provided by %v", entry.Key, entry.Func) 63 | } 64 | return b.String() 65 | } 66 | 67 | func (e errCycleDetected) writeMessage(w io.Writer, v string) { 68 | fmt.Fprint(w, e.Error()) 69 | } 70 | 71 | func (e errCycleDetected) Format(w fmt.State, c rune) { 72 | formatError(e, w, c) 73 | } 74 | 75 | // IsCycleDetected returns a boolean as to whether the provided error indicates 76 | // a cycle was detected in the container graph. 77 | func IsCycleDetected(err error) bool { 78 | return errors.As(err, &errCycleDetected{}) 79 | } 80 | -------------------------------------------------------------------------------- /decorate.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "fmt" 25 | "reflect" 26 | 27 | "go.uber.org/dig/internal/digreflect" 28 | "go.uber.org/dig/internal/dot" 29 | ) 30 | 31 | type decoratorState int 32 | 33 | const ( 34 | decoratorReady decoratorState = iota 35 | decoratorOnStack 36 | decoratorCalled 37 | ) 38 | 39 | type decorator interface { 40 | Call(c containerStore) error 41 | ID() dot.CtorID 42 | State() decoratorState 43 | } 44 | 45 | type decoratorNode struct { 46 | dcor interface{} 47 | dtype reflect.Type 48 | 49 | id dot.CtorID 50 | 51 | // Location where this function was defined. 52 | location *digreflect.Func 53 | 54 | // Current state of this decorator 55 | state decoratorState 56 | 57 | // Parameters of the decorator. 58 | params paramList 59 | 60 | // Results of the decorator. 61 | results resultList 62 | 63 | // Order of this node in each Scopes' graphHolders. 64 | orders map[*Scope]int 65 | 66 | // Scope this node was originally provided to. 67 | s *Scope 68 | 69 | // Callback for this decorator, if there is one. 70 | callback Callback 71 | 72 | // BeforeCallback for this decorator, if there is one 73 | beforeCallback BeforeCallback 74 | } 75 | 76 | func newDecoratorNode(dcor interface{}, s *Scope, opts decorateOptions) (*decoratorNode, error) { 77 | dval := reflect.ValueOf(dcor) 78 | dtype := dval.Type() 79 | dptr := dval.Pointer() 80 | 81 | pl, err := newParamList(dtype, s) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | rl, err := newResultList(dtype, resultOptions{}) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | n := &decoratorNode{ 92 | dcor: dcor, 93 | dtype: dtype, 94 | id: dot.CtorID(dptr), 95 | location: digreflect.InspectFunc(dcor), 96 | orders: make(map[*Scope]int), 97 | params: pl, 98 | results: rl, 99 | s: s, 100 | callback: opts.Callback, 101 | beforeCallback: opts.BeforeCallback, 102 | } 103 | return n, nil 104 | } 105 | 106 | func (n *decoratorNode) Call(s containerStore) (err error) { 107 | if n.state == decoratorCalled { 108 | return nil 109 | } 110 | 111 | n.state = decoratorOnStack 112 | 113 | if err := shallowCheckDependencies(s, n.params); err != nil { 114 | return errMissingDependencies{ 115 | Func: n.location, 116 | Reason: err, 117 | } 118 | } 119 | 120 | args, err := n.params.BuildList(n.s) 121 | if err != nil { 122 | return errArgumentsFailed{ 123 | Func: n.location, 124 | Reason: err, 125 | } 126 | } 127 | if n.beforeCallback != nil { 128 | n.beforeCallback(BeforeCallbackInfo{ 129 | Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name), 130 | }) 131 | } 132 | 133 | if n.callback != nil { 134 | start := s.clock().Now() 135 | // Wrap in separate func to include PanicErrors 136 | defer func() { 137 | n.callback(CallbackInfo{ 138 | Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name), 139 | Error: err, 140 | Runtime: s.clock().Since(start), 141 | }) 142 | }() 143 | } 144 | 145 | if n.s.recoverFromPanics { 146 | defer func() { 147 | if p := recover(); p != nil { 148 | err = PanicError{ 149 | fn: n.location, 150 | Panic: p, 151 | } 152 | } 153 | }() 154 | } 155 | 156 | results := s.invoker()(reflect.ValueOf(n.dcor), args) 157 | if err = n.results.ExtractList(n.s, true /* decorated */, results); err != nil { 158 | return err 159 | } 160 | n.state = decoratorCalled 161 | return nil 162 | } 163 | 164 | func (n *decoratorNode) ID() dot.CtorID { return n.id } 165 | 166 | func (n *decoratorNode) State() decoratorState { return n.state } 167 | 168 | // DecorateOption modifies the default behavior of Decorate. 169 | type DecorateOption interface { 170 | apply(*decorateOptions) 171 | } 172 | 173 | type decorateOptions struct { 174 | Info *DecorateInfo 175 | Callback Callback 176 | BeforeCallback BeforeCallback 177 | } 178 | 179 | // FillDecorateInfo is a DecorateOption that writes info on what Dig was 180 | // able to get out of the provided decorator into the provided DecorateInfo. 181 | func FillDecorateInfo(info *DecorateInfo) DecorateOption { 182 | return fillDecorateInfoOption{info: info} 183 | } 184 | 185 | type fillDecorateInfoOption struct{ info *DecorateInfo } 186 | 187 | func (o fillDecorateInfoOption) String() string { 188 | return fmt.Sprintf("FillDecorateInfo(%p)", o.info) 189 | } 190 | 191 | func (o fillDecorateInfoOption) apply(opts *decorateOptions) { 192 | opts.Info = o.info 193 | } 194 | 195 | // DecorateInfo provides information about the decorator's inputs and outputs 196 | // types as strings, as well as the ID of the decorator supplied to the Container. 197 | type DecorateInfo struct { 198 | ID ID 199 | Inputs []*Input 200 | Outputs []*Output 201 | } 202 | 203 | // Decorate provides a decorator for a type that has already been provided in the Container. 204 | // Decorations at this level affect all scopes of the container. 205 | // See Scope.Decorate for information on how to use this method. 206 | func (c *Container) Decorate(decorator interface{}, opts ...DecorateOption) error { 207 | return c.scope.Decorate(decorator, opts...) 208 | } 209 | 210 | // Decorate provides a decorator for a type that has already been provided in the Scope. 211 | // 212 | // Similar to Provide, Decorate takes in a function with zero or more dependencies and one 213 | // or more results. Decorate can be used to modify a type that was already introduced to the 214 | // Scope, or completely replace it with a new object. 215 | // 216 | // For example, 217 | // 218 | // s.Decorate(func(log *zap.Logger) *zap.Logger { 219 | // return log.Named("myapp") 220 | // }) 221 | // 222 | // This takes in a value, augments it with a name, and returns a replacement for it. Functions 223 | // in the Scope's dependency graph that use *zap.Logger will now use the *zap.Logger 224 | // returned by this decorator. 225 | // 226 | // A decorator can also take in multiple parameters and replace one of them: 227 | // 228 | // s.Decorate(func(log *zap.Logger, cfg *Config) *zap.Logger { 229 | // return log.Named(cfg.Name) 230 | // }) 231 | // 232 | // Or replace a subset of them: 233 | // 234 | // s.Decorate(func( 235 | // log *zap.Logger, 236 | // cfg *Config, 237 | // scope metrics.Scope 238 | // ) (*zap.Logger, metrics.Scope) { 239 | // log = log.Named(cfg.Name) 240 | // scope = scope.With(metrics.Tag("service", cfg.Name)) 241 | // return log, scope 242 | // }) 243 | // 244 | // Decorating a Scope affects all the child scopes of this Scope. 245 | // 246 | // Similar to a provider, the decorator function gets called *at most once*. 247 | func (s *Scope) Decorate(decorator interface{}, opts ...DecorateOption) error { 248 | var options decorateOptions 249 | for _, opt := range opts { 250 | opt.apply(&options) 251 | } 252 | 253 | dn, err := newDecoratorNode(decorator, s, options) 254 | if err != nil { 255 | return err 256 | } 257 | 258 | keys, err := findResultKeys(dn.results) 259 | if err != nil { 260 | return err 261 | } 262 | for _, k := range keys { 263 | if _, ok := s.decorators[k]; ok { 264 | return newErrInvalidInput( 265 | fmt.Sprintf("cannot decorate using function %v: %s already decorated", dn.dtype, k), nil) 266 | } 267 | s.decorators[k] = dn 268 | } 269 | 270 | if info := options.Info; info != nil { 271 | params := dn.params.DotParam() 272 | results := dn.results.DotResult() 273 | info.ID = (ID)(dn.id) 274 | info.Inputs = make([]*Input, len(params)) 275 | info.Outputs = make([]*Output, len(results)) 276 | 277 | for i, param := range params { 278 | info.Inputs[i] = &Input{ 279 | t: param.Type, 280 | optional: param.Optional, 281 | name: param.Name, 282 | group: param.Group, 283 | } 284 | } 285 | for i, res := range results { 286 | info.Outputs[i] = &Output{ 287 | t: res.Type, 288 | name: res.Name, 289 | group: res.Group, 290 | } 291 | } 292 | } 293 | return nil 294 | } 295 | 296 | func findResultKeys(r resultList) ([]key, error) { 297 | // use BFS to search for all keys included in a resultList. 298 | var ( 299 | q []result 300 | keys []key 301 | ) 302 | q = append(q, r) 303 | 304 | for len(q) > 0 { 305 | res := q[0] 306 | q = q[1:] 307 | 308 | switch innerResult := res.(type) { 309 | case resultSingle: 310 | keys = append(keys, key{t: innerResult.Type, name: innerResult.Name}) 311 | case resultGrouped: 312 | if innerResult.Type.Kind() != reflect.Slice { 313 | return nil, newErrInvalidInput("decorating a value group requires decorating the entire value group, not a single value", nil) 314 | } 315 | keys = append(keys, key{t: innerResult.Type.Elem(), group: innerResult.Group}) 316 | case resultObject: 317 | for _, f := range innerResult.Fields { 318 | q = append(q, f.Result) 319 | } 320 | case resultList: 321 | q = append(q, innerResult.Results...) 322 | } 323 | } 324 | return keys, nil 325 | } 326 | -------------------------------------------------------------------------------- /dig_int_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "math/rand" 25 | 26 | "go.uber.org/dig/internal/digclock" 27 | ) 28 | 29 | func SetRand(r *rand.Rand) Option { 30 | return setRand(r) 31 | } 32 | 33 | func SetClock(c digclock.Clock) Option { 34 | return setClock(c) 35 | } 36 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // Package dig provides an opinionated way of resolving object dependencies. 22 | // 23 | // # Status 24 | // 25 | // STABLE. No breaking changes will be made in this major version. 26 | // 27 | // # Container 28 | // 29 | // Dig exposes type Container as an object capable of resolving a directed 30 | // acyclic dependency graph. Use the New function to create one. 31 | // 32 | // c := dig.New() 33 | // 34 | // # Provide 35 | // 36 | // Constructors for different types are added to the container by using the 37 | // Provide method. A constructor can declare a dependency on another type by 38 | // simply adding it as a function parameter. Dependencies for a type can be 39 | // added to the graph both, before and after the type was added. 40 | // 41 | // err := c.Provide(func(conn *sql.DB) (*UserGateway, error) { 42 | // // ... 43 | // }) 44 | // if err != nil { 45 | // // ... 46 | // } 47 | // 48 | // if err := c.Provide(newDBConnection); err != nil { 49 | // // ... 50 | // } 51 | // 52 | // Multiple constructors can rely on the same type. The container creates a 53 | // singleton for each retained type, instantiating it at most once when 54 | // requested directly or as a dependency of another type. 55 | // 56 | // err := c.Provide(func(conn *sql.DB) *CommentGateway { 57 | // // ... 58 | // }) 59 | // if err != nil { 60 | // // ... 61 | // } 62 | // 63 | // Constructors can declare any number of dependencies as parameters and 64 | // optionally, return errors. 65 | // 66 | // err := c.Provide(func(u *UserGateway, c *CommentGateway) (*RequestHandler, error) { 67 | // // ... 68 | // }) 69 | // if err != nil { 70 | // // ... 71 | // } 72 | // 73 | // if err := c.Provide(newHTTPServer); err != nil { 74 | // // ... 75 | // } 76 | // 77 | // Constructors can also return multiple results to add multiple types to the 78 | // container. 79 | // 80 | // err := c.Provide(func(conn *sql.DB) (*UserGateway, *CommentGateway, error) { 81 | // // ... 82 | // }) 83 | // if err != nil { 84 | // // ... 85 | // } 86 | // 87 | // Constructors that accept a variadic number of arguments are treated as if 88 | // they don't have those arguments. That is, 89 | // 90 | // func NewVoteGateway(db *sql.DB, options ...Option) *VoteGateway 91 | // 92 | // Is treated the same as, 93 | // 94 | // func NewVoteGateway(db *sql.DB) *VoteGateway 95 | // 96 | // The constructor will be called with all other dependencies and no variadic 97 | // arguments. 98 | // 99 | // # Invoke 100 | // 101 | // Types added to the container may be consumed by using the Invoke method. 102 | // Invoke accepts any function that accepts one or more parameters and 103 | // optionally, returns an error. Dig calls the function with the requested 104 | // type, instantiating only those types that were requested by the function. 105 | // The call fails if any type or its dependencies (both direct and transitive) 106 | // were not available in the container. 107 | // 108 | // err := c.Invoke(func(l *log.Logger) { 109 | // // ... 110 | // }) 111 | // if err != nil { 112 | // // ... 113 | // } 114 | // 115 | // err := c.Invoke(func(server *http.Server) error { 116 | // // ... 117 | // }) 118 | // if err != nil { 119 | // // ... 120 | // } 121 | // 122 | // Any error returned by the invoked function is propagated back to the 123 | // caller. 124 | // 125 | // # Parameter Objects 126 | // 127 | // Constructors declare their dependencies as function parameters. This can 128 | // very quickly become unreadable if the constructor has a lot of 129 | // dependencies. 130 | // 131 | // func NewHandler(users *UserGateway, comments *CommentGateway, posts *PostGateway, votes *VoteGateway, authz *AuthZGateway) *Handler { 132 | // // ... 133 | // } 134 | // 135 | // A pattern employed to improve readability in a situation like this is to 136 | // create a struct that lists all the parameters of the function as fields and 137 | // changing the function to accept that struct instead. This is referred to as 138 | // a parameter object. 139 | // 140 | // Dig has first class support for parameter objects: any struct embedding 141 | // dig.In gets treated as a parameter object. The following is equivalent to 142 | // the constructor above. 143 | // 144 | // type HandlerParams struct { 145 | // dig.In 146 | // 147 | // Users *UserGateway 148 | // Comments *CommentGateway 149 | // Posts *PostGateway 150 | // Votes *VoteGateway 151 | // AuthZ *AuthZGateway 152 | // } 153 | // 154 | // func NewHandler(p HandlerParams) *Handler { 155 | // // ... 156 | // } 157 | // 158 | // Handlers can receive any combination of parameter objects and parameters. 159 | // 160 | // func NewHandler(p HandlerParams, l *log.Logger) *Handler { 161 | // // ... 162 | // } 163 | // 164 | // # Result Objects 165 | // 166 | // Result objects are the flip side of parameter objects. These are structs 167 | // that represent multiple outputs from a single function as fields in the 168 | // struct. Structs embedding dig.Out get treated as result objects. 169 | // 170 | // func SetupGateways(conn *sql.DB) (*UserGateway, *CommentGateway, *PostGateway, error) { 171 | // // ... 172 | // } 173 | // 174 | // The above is equivalent to, 175 | // 176 | // type Gateways struct { 177 | // dig.Out 178 | // 179 | // Users *UserGateway 180 | // Comments *CommentGateway 181 | // Posts *PostGateway 182 | // } 183 | // 184 | // func SetupGateways(conn *sql.DB) (Gateways, error) { 185 | // // ... 186 | // } 187 | // 188 | // # Optional Dependencies 189 | // 190 | // Constructors often don't have a hard dependency on some types and 191 | // are able to operate in a degraded state when that dependency is missing. 192 | // Dig supports declaring dependencies as optional by adding an 193 | // `optional:"true"` tag to fields of a dig.In struct. 194 | // 195 | // Fields in a dig.In structs that have the `optional:"true"` tag are treated 196 | // as optional by Dig. 197 | // 198 | // type UserGatewayParams struct { 199 | // dig.In 200 | // 201 | // Conn *sql.DB 202 | // Cache *redis.Client `optional:"true"` 203 | // } 204 | // 205 | // If an optional field is not available in the container, the constructor 206 | // will receive a zero value for the field. 207 | // 208 | // func NewUserGateway(p UserGatewayParams, log *log.Logger) (*UserGateway, error) { 209 | // if p.Cache == nil { 210 | // log.Print("Caching disabled") 211 | // } 212 | // // ... 213 | // } 214 | // 215 | // Constructors that declare dependencies as optional MUST handle the case of 216 | // those dependencies being absent. 217 | // 218 | // The optional tag also allows adding new dependencies without breaking 219 | // existing consumers of the constructor. 220 | // 221 | // # Named Values 222 | // 223 | // Some use cases call for multiple values of the same type. Dig allows adding 224 | // multiple values of the same type to the container with the use of Named 225 | // Values. 226 | // 227 | // Named Values can be produced by passing the dig.Name option when a 228 | // constructor is provided. All values produced by that constructor will have 229 | // the given name. 230 | // 231 | // Given the following constructors, 232 | // 233 | // func NewReadOnlyConnection(...) (*sql.DB, error) 234 | // func NewReadWriteConnection(...) (*sql.DB, error) 235 | // 236 | // You can provide *sql.DB into a Container under different names by passing 237 | // the dig.Name option. 238 | // 239 | // c.Provide(NewReadOnlyConnection, dig.Name("ro")) 240 | // c.Provide(NewReadWriteConnection, dig.Name("rw")) 241 | // 242 | // Alternatively, you can produce a dig.Out struct and tag its fields with 243 | // `name:".."` to have the corresponding value added to the graph under the 244 | // specified name. 245 | // 246 | // type ConnectionResult struct { 247 | // dig.Out 248 | // 249 | // ReadWrite *sql.DB `name:"rw"` 250 | // ReadOnly *sql.DB `name:"ro"` 251 | // } 252 | // 253 | // func ConnectToDatabase(...) (ConnectionResult, error) { 254 | // // ... 255 | // return ConnectionResult{ReadWrite: rw, ReadOnly: ro}, nil 256 | // } 257 | // 258 | // Regardless of how a Named Value was produced, it can be consumed by another 259 | // constructor by accepting a dig.In struct which has exported fields with the 260 | // same name AND type that you provided. 261 | // 262 | // type GatewayParams struct { 263 | // dig.In 264 | // 265 | // WriteToConn *sql.DB `name:"rw"` 266 | // ReadFromConn *sql.DB `name:"ro"` 267 | // } 268 | // 269 | // The name tag may be combined with the optional tag to declare the 270 | // dependency optional. 271 | // 272 | // type GatewayParams struct { 273 | // dig.In 274 | // 275 | // WriteToConn *sql.DB `name:"rw"` 276 | // ReadFromConn *sql.DB `name:"ro" optional:"true"` 277 | // } 278 | // 279 | // func NewCommentGateway(p GatewayParams, log *log.Logger) (*CommentGateway, error) { 280 | // if p.ReadFromConn == nil { 281 | // log.Print("Warning: Using RW connection for reads") 282 | // p.ReadFromConn = p.WriteToConn 283 | // } 284 | // // ... 285 | // } 286 | // 287 | // # Value Groups 288 | // 289 | // Added in Dig 1.2. 290 | // 291 | // Dig provides value groups to allow producing and consuming many values of 292 | // the same type. Value groups allow constructors to send values to a named, 293 | // unordered collection in the container. Other constructors can request all 294 | // values in this collection as a slice. 295 | // 296 | // Constructors can send values into value groups by returning a dig.Out 297 | // struct tagged with `group:".."`. 298 | // 299 | // type HandlerResult struct { 300 | // dig.Out 301 | // 302 | // Handler Handler `group:"server"` 303 | // } 304 | // 305 | // func NewHelloHandler() HandlerResult { 306 | // .. 307 | // } 308 | // 309 | // func NewEchoHandler() HandlerResult { 310 | // .. 311 | // } 312 | // 313 | // Any number of constructors may provide values to this named collection. 314 | // Other constructors can request all values for this collection by requesting 315 | // a slice tagged with `group:".."`. This will execute all constructors that 316 | // provide a value to that group in an unspecified order. 317 | // 318 | // type ServerParams struct { 319 | // dig.In 320 | // 321 | // Handlers []Handler `group:"server"` 322 | // } 323 | // 324 | // func NewServer(p ServerParams) *Server { 325 | // server := newServer() 326 | // for _, h := range p.Handlers { 327 | // server.Register(h) 328 | // } 329 | // return server 330 | // } 331 | // 332 | // Note that values in a value group are unordered. Dig makes no guarantees 333 | // about the order in which these values will be produced. 334 | // 335 | // Value groups can be used to provide multiple values for a group from a 336 | // dig.Out using slices, however considering groups are retrieved by requesting 337 | // a slice this implies that the values must be retrieved using a slice of 338 | // slices. As of dig v1.9.0, if you want to provide individual elements to the 339 | // group instead of the slice itself, you can add the `flatten` modifier to the 340 | // group from a dig.Out. 341 | // 342 | // type IntResult struct { 343 | // dig.Out 344 | // 345 | // Handler []int `group:"server"` // [][]int from dig.In 346 | // Handler []int `group:"server,flatten"` // []int from dig.In 347 | // } 348 | package dig // import "go.uber.org/dig" 349 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig_test 22 | 23 | import ( 24 | "encoding/json" 25 | "log" 26 | "os" 27 | 28 | "go.uber.org/dig" 29 | ) 30 | 31 | func Example_minimal() { 32 | type Config struct { 33 | Prefix string 34 | } 35 | 36 | c := dig.New() 37 | 38 | // Provide a Config object. This can fail to decode. 39 | err := c.Provide(func() (*Config, error) { 40 | // In a real program, the configuration will probably be read from a 41 | // file. 42 | var cfg Config 43 | err := json.Unmarshal([]byte(`{"prefix": "[foo] "}`), &cfg) 44 | return &cfg, err 45 | }) 46 | if err != nil { 47 | panic(err) 48 | } 49 | 50 | // Provide a way to build the logger based on the configuration. 51 | err = c.Provide(func(cfg *Config) *log.Logger { 52 | return log.New(os.Stdout, cfg.Prefix, 0) 53 | }) 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | // Invoke a function that requires the logger, which in turn builds the 59 | // Config first. 60 | err = c.Invoke(func(l *log.Logger) { 61 | l.Print("You've been invoked") 62 | }) 63 | if err != nil { 64 | panic(err) 65 | } 66 | 67 | // Output: 68 | // [foo] You've been invoked 69 | } 70 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module go.uber.org/dig 2 | 3 | go 1.20 4 | 5 | require github.com/stretchr/testify v1.7.1 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 | 13 | retract v1.16.0 // bad release 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 8 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 12 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 13 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 14 | -------------------------------------------------------------------------------- /graph.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import "go.uber.org/dig/internal/graph" 24 | 25 | // graphNode is a single node in the dependency graph. 26 | type graphNode struct { 27 | Wrapped interface{} 28 | } 29 | 30 | // graphHolder is the dependency graph of the container. 31 | // It saves constructorNodes and paramGroupedSlice (value groups) 32 | // as nodes in the graph. 33 | // It implements the graph interface defined by internal/graph. 34 | // It has 1-1 correspondence with the Scope whose graph it represents. 35 | type graphHolder struct { 36 | // all the nodes defined in the graph. 37 | nodes []*graphNode 38 | 39 | // Scope whose graph this holder contains. 40 | s *Scope 41 | 42 | // Number of nodes in the graph at last snapshot. 43 | // -1 if no snapshot has been taken. 44 | snap int 45 | } 46 | 47 | var _ graph.Graph = (*graphHolder)(nil) 48 | 49 | func newGraphHolder(s *Scope) *graphHolder { 50 | return &graphHolder{s: s, snap: -1} 51 | } 52 | 53 | func (gh *graphHolder) Order() int { return len(gh.nodes) } 54 | 55 | // EdgesFrom returns the indices of nodes that are dependencies of node u. 56 | // 57 | // To do that, it needs to do one of the following: 58 | // 59 | // For constructor nodes, it retrieves the providers of the constructor's 60 | // parameters from the container and reports their orders. 61 | // 62 | // For value group nodes, it retrieves the group providers from the container 63 | // and reports their orders. 64 | func (gh *graphHolder) EdgesFrom(u int) []int { 65 | var orders []int 66 | switch w := gh.Lookup(u).(type) { 67 | case *constructorNode: 68 | for _, param := range w.paramList.Params { 69 | orders = append(orders, getParamOrder(gh, param)...) 70 | } 71 | case *paramGroupedSlice: 72 | providers := gh.s.getAllGroupProviders(w.Group, w.Type.Elem()) 73 | for _, provider := range providers { 74 | orders = append(orders, provider.Order(gh.s)) 75 | } 76 | } 77 | return orders 78 | } 79 | 80 | // NewNode adds a new value to the graph and returns its order. 81 | func (gh *graphHolder) NewNode(wrapped interface{}) int { 82 | order := len(gh.nodes) 83 | gh.nodes = append(gh.nodes, &graphNode{ 84 | Wrapped: wrapped, 85 | }) 86 | return order 87 | } 88 | 89 | // Lookup retrieves the value for the node with the given order. 90 | // Lookup panics if i is invalid. 91 | func (gh *graphHolder) Lookup(i int) interface{} { 92 | return gh.nodes[i].Wrapped 93 | } 94 | 95 | // Snapshot takes a temporary snapshot of the current state of the graph. 96 | // Use with Rollback to undo changes to the graph. 97 | // 98 | // Only one snapshot is allowed at a time. 99 | // Multiple calls to snapshot will overwrite prior snapshots. 100 | func (gh *graphHolder) Snapshot() { 101 | gh.snap = len(gh.nodes) 102 | } 103 | 104 | // Rollback rolls back a snapshot to a previously captured state. 105 | // This is a no-op if no snapshot was captured. 106 | func (gh *graphHolder) Rollback() { 107 | if gh.snap < 0 { 108 | return 109 | } 110 | 111 | // nodes is an append-only list. To rollback, we just drop the 112 | // extraneous entries from the slice. 113 | gh.nodes = gh.nodes[:gh.snap] 114 | gh.snap = -1 115 | } 116 | -------------------------------------------------------------------------------- /group.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "fmt" 25 | "io" 26 | "strings" 27 | ) 28 | 29 | const ( 30 | _groupTag = "group" 31 | ) 32 | 33 | type group struct { 34 | Name string 35 | Flatten bool 36 | Soft bool 37 | } 38 | 39 | type errInvalidGroupOption struct{ Option string } 40 | 41 | var _ digError = errInvalidGroupOption{} 42 | 43 | func (e errInvalidGroupOption) Error() string { return fmt.Sprint(e) } 44 | 45 | func (e errInvalidGroupOption) writeMessage(w io.Writer, v string) { 46 | fmt.Fprintf(w, "invalid option %q", e.Option) 47 | } 48 | 49 | func (e errInvalidGroupOption) Format(w fmt.State, c rune) { 50 | formatError(e, w, c) 51 | } 52 | 53 | func parseGroupString(s string) (group, error) { 54 | components := strings.Split(s, ",") 55 | g := group{Name: components[0]} 56 | for _, c := range components[1:] { 57 | switch c { 58 | case "flatten": 59 | g.Flatten = true 60 | case "soft": 61 | g.Soft = true 62 | default: 63 | return g, errInvalidGroupOption{Option: c} 64 | } 65 | } 66 | return g, nil 67 | } 68 | -------------------------------------------------------------------------------- /group_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestParseGroup(t *testing.T) { 30 | tests := []struct { 31 | name string 32 | group string 33 | wantG group 34 | wantErr string 35 | }{ 36 | { 37 | name: "simple group", 38 | group: `somegroup`, 39 | wantG: group{Name: "somegroup"}, 40 | }, 41 | { 42 | name: "flattened group", 43 | group: `somegroup,flatten`, 44 | wantG: group{Name: "somegroup", Flatten: true}, 45 | }, 46 | { 47 | name: "soft group", 48 | group: "somegroup,soft", 49 | wantG: group{Name: "somegroup", Soft: true}, 50 | }, 51 | { 52 | name: "error", 53 | group: `somegroup,abc`, 54 | wantErr: `invalid option "abc"`, 55 | }, 56 | } 57 | for _, tt := range tests { 58 | t.Run(tt.name, func(t *testing.T) { 59 | gotG, err := parseGroupString(tt.group) 60 | if tt.wantErr != "" { 61 | assert.Error(t, err) 62 | assert.Contains(t, err.Error(), tt.wantErr) 63 | return 64 | } 65 | assert.Equal(t, tt.wantG, gotG) 66 | }) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /inout.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "container/list" 25 | "fmt" 26 | "reflect" 27 | "strconv" 28 | ) 29 | 30 | var ( 31 | _noValue reflect.Value 32 | _errType = reflect.TypeOf((*error)(nil)).Elem() 33 | _inPtrType = reflect.TypeOf((*In)(nil)) 34 | _inType = reflect.TypeOf(In{}) 35 | _outPtrType = reflect.TypeOf((*Out)(nil)) 36 | _outType = reflect.TypeOf(Out{}) 37 | ) 38 | 39 | // Placeholder type placed in dig.In/dig.out to make their special nature 40 | // obvious in godocs. 41 | // Otherwise they will appear as plain empty structs. 42 | type digSentinel struct{} 43 | 44 | // In may be embedded into structs to request dig to treat them as special 45 | // parameter structs. When a constructor accepts such a struct, instead of the 46 | // struct becoming a dependency for that constructor, all its fields become 47 | // dependencies instead. See the section on Parameter Objects in the 48 | // package-level documentation for more information. 49 | // 50 | // Fields of the struct may optionally be tagged to customize the behavior of 51 | // dig. The following tags are supported, 52 | // 53 | // name Requests a value with the same name and type from the 54 | // container. See Named Values for more information. 55 | // optional If set to true, indicates that the dependency is optional and 56 | // the constructor gracefully handles its absence. 57 | // group Name of the Value Group from which this field will be filled. 58 | // The field must be a slice type. See Value Groups in the 59 | // package documentation for more information. 60 | type In struct{ _ digSentinel } 61 | 62 | // Out is an embeddable type that signals to dig that the returned 63 | // struct should be treated differently. Instead of the struct itself 64 | // becoming part of the container, all members of the struct will. 65 | 66 | // Out may be embedded into structs to request dig to treat them as special 67 | // result structs. When a constructor returns such a struct, instead of the 68 | // struct becoming a result of the constructor, all its fields become results 69 | // of the constructor. See the section on Result Objects in the package-level 70 | // documentation for more information. 71 | // 72 | // Fields of the struct may optionally be tagged to customize the behavior of 73 | // dig. The following tags are supported, 74 | // 75 | // name Specifies the name of the value. Only a field on a dig.In 76 | // struct with the same 'name' annotation can receive this 77 | // value. See Named Values for more information. 78 | // group Name of the Value Group to which this field's value is being 79 | // sent. See Value Groups in the package documentation for more 80 | // information. 81 | type Out struct{ _ digSentinel } 82 | 83 | func isError(t reflect.Type) bool { 84 | return t.Implements(_errType) 85 | } 86 | 87 | // IsIn checks whether the given struct is a dig.In struct. A struct qualifies 88 | // as a dig.In struct if it embeds the dig.In type or if any struct that it 89 | // embeds is a dig.In struct. The parameter may be the reflect.Type of the 90 | // struct rather than the struct itself. 91 | // 92 | // A struct MUST qualify as a dig.In struct for its fields to be treated 93 | // specially by dig. 94 | // 95 | // See the documentation for dig.In for a comprehensive list of supported 96 | // tags. 97 | func IsIn(o interface{}) bool { 98 | return embedsType(o, _inType) 99 | } 100 | 101 | // IsOut checks whether the given struct is a dig.Out struct. A struct 102 | // qualifies as a dig.Out struct if it embeds the dig.Out type or if any 103 | // struct that it embeds is a dig.Out struct. The parameter may be the 104 | // reflect.Type of the struct rather than the struct itself. 105 | // 106 | // A struct MUST qualify as a dig.Out struct for its fields to be treated 107 | // specially by dig. 108 | // 109 | // See the documentation for dig.Out for a comprehensive list of supported 110 | // tags. 111 | func IsOut(o interface{}) bool { 112 | return embedsType(o, _outType) 113 | } 114 | 115 | // Returns true if t embeds e or if any of the types embedded by t embed e. 116 | func embedsType(i interface{}, e reflect.Type) bool { 117 | // TODO: this function doesn't consider e being a pointer. 118 | // given `type A foo { *In }`, this function would return false for 119 | // embedding dig.In, which makes for some extra error checking in places 120 | // that call this function. Might be worthwhile to consider reflect.Indirect 121 | // usage to clean up the callers. 122 | 123 | if i == nil { 124 | return false 125 | } 126 | 127 | // maybe it's already a reflect.Type 128 | t, ok := i.(reflect.Type) 129 | if !ok { 130 | // take the type if it's not 131 | t = reflect.TypeOf(i) 132 | } 133 | 134 | // We are going to do a breadth-first search of all embedded fields. 135 | types := list.New() 136 | types.PushBack(t) 137 | for types.Len() > 0 { 138 | t := types.Remove(types.Front()).(reflect.Type) 139 | 140 | if t == e { 141 | return true 142 | } 143 | 144 | if t.Kind() != reflect.Struct { 145 | continue 146 | } 147 | 148 | for i := 0; i < t.NumField(); i++ { 149 | f := t.Field(i) 150 | if f.Anonymous { 151 | types.PushBack(f.Type) 152 | } 153 | } 154 | } 155 | 156 | // If perf is an issue, we can cache known In objects and Out objects in a 157 | // map[reflect.Type]struct{}. 158 | return false 159 | } 160 | 161 | // Checks if a field of an In struct is optional. 162 | func isFieldOptional(f reflect.StructField) (bool, error) { 163 | tag := f.Tag.Get(_optionalTag) 164 | if tag == "" { 165 | return false, nil 166 | } 167 | 168 | optional, err := strconv.ParseBool(tag) 169 | if err != nil { 170 | err = newErrInvalidInput( 171 | fmt.Sprintf("invalid value %q for %q tag on field %v", tag, _optionalTag, f.Name), err) 172 | } 173 | 174 | return optional, err 175 | } 176 | -------------------------------------------------------------------------------- /internal/digclock/clock.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package digclock 22 | 23 | import ( 24 | "time" 25 | ) 26 | 27 | // Clock defines how dig accesses time. 28 | type Clock interface { 29 | Now() time.Time 30 | Since(time.Time) time.Duration 31 | } 32 | 33 | // System is the default implementation of Clock based on real time. 34 | var System Clock = systemClock{} 35 | 36 | type systemClock struct{} 37 | 38 | func (systemClock) Now() time.Time { 39 | return time.Now() 40 | } 41 | 42 | func (systemClock) Since(t time.Time) time.Duration { 43 | return time.Since(t) 44 | } 45 | 46 | // Mock is a fake source of time. 47 | // It implements standard time operations, but allows 48 | // the user to control the passage of time. 49 | // 50 | // Use the [Mock.Add] method to progress time. 51 | // 52 | // Note that this implementation is not safe for concurrent use. 53 | type Mock struct { 54 | now time.Time 55 | } 56 | 57 | var _ Clock = (*Mock)(nil) 58 | 59 | // NewMock creates a new mock clock with the current time set to the current time. 60 | func NewMock() *Mock { 61 | return &Mock{now: time.Now()} 62 | } 63 | 64 | // Now returns the current time. 65 | func (m *Mock) Now() time.Time { 66 | return m.now 67 | } 68 | 69 | // Since returns the time elapsed since the given time. 70 | func (m *Mock) Since(t time.Time) time.Duration { 71 | return m.Now().Sub(t) 72 | } 73 | 74 | // Add progresses time by the given duration. 75 | // 76 | // It panics if the duration is negative. 77 | func (m *Mock) Add(d time.Duration) { 78 | if d < 0 { 79 | panic("cannot add negative duration") 80 | } 81 | m.now = m.now.Add(d) 82 | } 83 | -------------------------------------------------------------------------------- /internal/digclock/clock_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package digclock 22 | 23 | import ( 24 | "testing" 25 | "time" 26 | 27 | "github.com/stretchr/testify/assert" 28 | ) 29 | 30 | func TestSystemClock(t *testing.T) { 31 | clock := System 32 | testClock(t, clock, func(d time.Duration) { time.Sleep(d) }) 33 | } 34 | 35 | func TestMockClock(t *testing.T) { 36 | clock := NewMock() 37 | testClock(t, clock, clock.Add) 38 | } 39 | 40 | func testClock(t *testing.T, clock Clock, advance func(d time.Duration)) { 41 | now := clock.Now() 42 | assert.False(t, now.IsZero()) 43 | 44 | t.Run("Since", func(t *testing.T) { 45 | advance(1 * time.Millisecond) 46 | assert.NotZero(t, clock.Since(now), "time must have advanced") 47 | }) 48 | } 49 | 50 | func TestMock_AddNegative(t *testing.T) { 51 | clock := NewMock() 52 | assert.Panics(t, func() { clock.Add(-1) }) 53 | } 54 | -------------------------------------------------------------------------------- /internal/digerror/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package digerror 22 | 23 | import ( 24 | "fmt" 25 | ) 26 | 27 | // BugPanicf panics with the provided message directing users to GitHub issues 28 | // creation page. 29 | func BugPanicf(msg string, args ...interface{}) { 30 | panic(fmt.Sprintf("It looks like you have found a bug in dig. "+ 31 | "Please file an issue at https://github.com/uber-go/dig/issues/new "+ 32 | "and provide the following message: "+ 33 | msg, args...)) 34 | } 35 | -------------------------------------------------------------------------------- /internal/digreflect/func.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package digreflect 22 | 23 | import ( 24 | "fmt" 25 | "net/url" 26 | "reflect" 27 | "runtime" 28 | "strings" 29 | ) 30 | 31 | // Func contains runtime information about a function. 32 | type Func struct { 33 | // Name of the function. 34 | Name string 35 | 36 | // Name of the package in which this function is defined. 37 | Package string 38 | 39 | // Path to the file in which this function is defined. 40 | File string 41 | 42 | // Line number in the file at which this function is defined. 43 | Line int 44 | } 45 | 46 | // String returns a string representation of the function. 47 | func (f *Func) String() string { 48 | return fmt.Sprint(f) 49 | } 50 | 51 | // Format implements fmt.Formatter for Func, printing a single-line 52 | // representation for %v and a multi-line one for %+v. 53 | func (f *Func) Format(w fmt.State, c rune) { 54 | if w.Flag('+') && c == 'v' { 55 | // "path/to/package".MyFunction 56 | // path/to/file.go:42 57 | fmt.Fprintf(w, "%q.%v", f.Package, f.Name) 58 | fmt.Fprintf(w, "\n\t%v:%v", f.File, f.Line) 59 | } else { 60 | // "path/to/package".MyFunction (path/to/file.go:42) 61 | fmt.Fprintf(w, "%q.%v (%v:%v)", f.Package, f.Name, f.File, f.Line) 62 | } 63 | } 64 | 65 | // InspectFunc inspects and returns runtime information about the given 66 | // function. 67 | func InspectFunc(function interface{}) *Func { 68 | fptr := reflect.ValueOf(function).Pointer() 69 | return InspectFuncPC(fptr) 70 | } 71 | 72 | // InspectFuncPC inspects and returns runtime information about the function 73 | // at the given program counter address. 74 | func InspectFuncPC(pc uintptr) *Func { 75 | f := runtime.FuncForPC(pc) 76 | if f == nil { 77 | return nil 78 | } 79 | pkgName, funcName := splitFuncName(f.Name()) 80 | fileName, lineNum := f.FileLine(pc) 81 | return &Func{ 82 | Name: funcName, 83 | Package: pkgName, 84 | File: fileName, 85 | Line: lineNum, 86 | } 87 | } 88 | 89 | const _vendor = "/vendor/" 90 | 91 | func splitFuncName(function string) (pname string, fname string) { 92 | if len(function) == 0 { 93 | return 94 | } 95 | 96 | // We have something like "path.to/my/pkg.MyFunction". If the function is 97 | // a closure, it is something like, "path.to/my/pkg.MyFunction.func1". 98 | 99 | idx := 0 100 | 101 | // Everything up to the first "." after the last "/" is the package name. 102 | // Everything after the "." is the full function name. 103 | if i := strings.LastIndex(function, "/"); i >= 0 { 104 | idx = i 105 | } 106 | if i := strings.Index(function[idx:], "."); i >= 0 { 107 | idx += i 108 | } 109 | pname, fname = function[:idx], function[idx+1:] 110 | 111 | // The package may be vendored. 112 | if i := strings.Index(pname, _vendor); i > 0 { 113 | pname = pname[i+len(_vendor):] 114 | } 115 | 116 | // Package names are URL-encoded to avoid ambiguity in the case where the 117 | // package name contains ".git". Otherwise, "foo/bar.git.MyFunction" would 118 | // mean that "git" is the top-level function and "MyFunction" is embedded 119 | // inside it. 120 | if unescaped, err := url.QueryUnescape(pname); err == nil { 121 | pname = unescaped 122 | } 123 | 124 | return 125 | } 126 | -------------------------------------------------------------------------------- /internal/digreflect/func_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package digreflect 22 | 23 | import ( 24 | "fmt" 25 | "strings" 26 | "testing" 27 | 28 | "github.com/stretchr/testify/assert" 29 | myrepository "go.uber.org/dig/internal/digreflect/tests/myrepository.git" 30 | mypackage "go.uber.org/dig/internal/digreflect/tests/myrepository.git/mypackage" 31 | ) 32 | 33 | func SomeExportedFunction() {} 34 | 35 | func unexportedFunction() {} 36 | 37 | func nestedFunctions() (nested1, nested2, nested3 func()) { 38 | // we call the functions to satisfy the linter. 39 | nested1 = func() {} 40 | nested2 = func() { 41 | nested3 = func() {} 42 | } 43 | nested2() // set nested3 44 | return 45 | } 46 | 47 | func TestInspectFunc(t *testing.T) { 48 | nested1, nested2, nested3 := nestedFunctions() 49 | 50 | tests := []struct { 51 | desc string 52 | give interface{} 53 | wantName string 54 | wantPackage string 55 | 56 | // We don't match the exact file name because $GOPATH can be anywhere 57 | // on someone's system. Instead we'll match the suffix. 58 | wantFileSuffix string 59 | }{ 60 | { 61 | desc: "exported function", 62 | give: SomeExportedFunction, 63 | wantName: "SomeExportedFunction", 64 | wantPackage: "go.uber.org/dig/internal/digreflect", 65 | wantFileSuffix: "/internal/digreflect/func_test.go", 66 | }, 67 | { 68 | desc: "unexported function", 69 | give: unexportedFunction, 70 | wantName: "unexportedFunction", 71 | wantPackage: "go.uber.org/dig/internal/digreflect", 72 | wantFileSuffix: "/internal/digreflect/func_test.go", 73 | }, 74 | { 75 | desc: "nested function", 76 | give: nested1, 77 | wantName: "nestedFunctions.func1", 78 | wantPackage: "go.uber.org/dig/internal/digreflect", 79 | wantFileSuffix: "/internal/digreflect/func_test.go", 80 | }, 81 | { 82 | desc: "second nested function", 83 | give: nested2, 84 | wantName: "nestedFunctions.func2", 85 | wantPackage: "go.uber.org/dig/internal/digreflect", 86 | wantFileSuffix: "/internal/digreflect/func_test.go", 87 | }, 88 | { 89 | desc: "nested inside a nested function", 90 | give: nested3, 91 | wantName: "nestedFunctions.func2.1", 92 | wantPackage: "go.uber.org/dig/internal/digreflect", 93 | wantFileSuffix: "/internal/digreflect/func_test.go", 94 | }, 95 | { 96 | desc: "inside a .git package", 97 | give: myrepository.Hello, 98 | wantName: "Hello", 99 | wantPackage: "go.uber.org/dig/internal/digreflect/tests/myrepository.git", 100 | wantFileSuffix: "/internal/digreflect/tests/myrepository.git/hello.go", 101 | }, 102 | { 103 | desc: "subpackage of a .git package", 104 | give: mypackage.Add, 105 | wantName: "Add", 106 | wantPackage: "go.uber.org/dig/internal/digreflect/tests/myrepository.git/mypackage", 107 | wantFileSuffix: "/internal/digreflect/tests/myrepository.git/mypackage/add.go", 108 | }, 109 | { 110 | desc: "dependency", 111 | give: assert.Contains, 112 | wantName: "Contains", 113 | wantPackage: "github.com/stretchr/testify/assert", 114 | wantFileSuffix: "/assert/assertions.go", 115 | }, 116 | } 117 | 118 | for _, tt := range tests { 119 | t.Run(tt.desc, func(t *testing.T) { 120 | f := InspectFunc(tt.give) 121 | assert.Equal(t, tt.wantName, f.Name, "function name did not match") 122 | assert.Equal(t, tt.wantPackage, f.Package, "package name did not match") 123 | 124 | assert.True(t, strings.HasSuffix(f.File, tt.wantFileSuffix), 125 | "file path %q does not end with src/%v", f.File, tt.wantFileSuffix) 126 | }) 127 | } 128 | } 129 | 130 | func TestSplitFunc(t *testing.T) { 131 | t.Run("empty string", func(t *testing.T) { 132 | pname, fname := splitFuncName("") 133 | assert.Empty(t, pname, "package name must be empty") 134 | assert.Empty(t, fname, "function name must be empty") 135 | }) 136 | 137 | t.Run("vendored dependency", func(t *testing.T) { 138 | pname, fname := splitFuncName("go.uber.orgc/dig/vendor/example.com/foo/bar.Baz") 139 | assert.Equal(t, "example.com/foo/bar", pname) 140 | assert.Equal(t, "Baz", fname) 141 | }) 142 | } 143 | 144 | func TestFuncFormatting(t *testing.T) { 145 | f := Func{ 146 | Package: "foo/bar/baz", 147 | Name: "Qux", 148 | File: "src/foo/bar/baz/qux.go", 149 | Line: 42, 150 | } 151 | 152 | assert.Equal(t, 153 | `"foo/bar/baz".Qux (src/foo/bar/baz/qux.go:42)`, 154 | f.String(), "%v did not match") 155 | 156 | assert.Equal(t, `"foo/bar/baz".Qux 157 | src/foo/bar/baz/qux.go:42`, fmt.Sprintf("%+v", &f), "%v did not match") 158 | } 159 | -------------------------------------------------------------------------------- /internal/digreflect/tests/myrepository.git/hello.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package myrepository 22 | 23 | import "fmt" 24 | 25 | // Hello says hello. 26 | func Hello() { 27 | fmt.Println("hello") 28 | } 29 | -------------------------------------------------------------------------------- /internal/digreflect/tests/myrepository.git/mypackage/add.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package mypackage 22 | 23 | import "fmt" 24 | 25 | // Add adds numbers. 26 | func Add(a, b int) { 27 | fmt.Println(a + b) 28 | } 29 | -------------------------------------------------------------------------------- /internal/digtest/container.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package digtest 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/require" 27 | "go.uber.org/dig" 28 | ) 29 | 30 | // Container wraps dig.Container to provide methods for easier testing. 31 | type Container struct { 32 | *dig.Container 33 | 34 | t testing.TB 35 | } 36 | 37 | // Scope wraps dig.Scope to provide methods for easier testing. 38 | type Scope struct { 39 | *scope 40 | 41 | t testing.TB 42 | } 43 | 44 | // scope is an alias of dig.Scope that allows us to embed dig.Scope into 45 | // digtest.Scope, while still having a method named Scope on it. 46 | // 47 | // Without this, digtest.Scope cannot have a Scope method because it has 48 | // an exported attribute Scope (the embedded dig.Scope) field. That is, 49 | // 50 | // type Foo struct{ *dig.Scope } 51 | // 52 | // func (*Foo) Scope() 53 | // 54 | // The above is illegal because it's unclear what Foo.Scope refers to: the 55 | // attribute or the method. 56 | type scope = dig.Scope 57 | 58 | // New builds a new testing container. 59 | func New(t testing.TB, opts ...dig.Option) *Container { 60 | return &Container{ 61 | t: t, 62 | Container: dig.New(opts...), 63 | } 64 | } 65 | 66 | // RequireProvide provides the given function to the container, 67 | // halting the test if it fails. 68 | func (c *Container) RequireProvide(f interface{}, opts ...dig.ProvideOption) { 69 | c.t.Helper() 70 | 71 | require.NoError(c.t, c.Provide(f, opts...), "failed to provide") 72 | } 73 | 74 | // RequireProvide provides the given function to the scope, 75 | // halting the test if it fails. 76 | func (s *Scope) RequireProvide(f interface{}, opts ...dig.ProvideOption) { 77 | s.t.Helper() 78 | 79 | require.NoError(s.t, s.Provide(f, opts...), "failed to provide") 80 | } 81 | 82 | // RequireInvoke invokes the given function to the container, 83 | // halting the test if it fails. 84 | func (c *Container) RequireInvoke(f interface{}, opts ...dig.InvokeOption) { 85 | c.t.Helper() 86 | 87 | require.NoError(c.t, c.Invoke(f, opts...), "failed to invoke") 88 | } 89 | 90 | // RequireInvoke invokes the given function to the scope, 91 | // halting the test if it fails. 92 | func (s *Scope) RequireInvoke(f interface{}, opts ...dig.InvokeOption) { 93 | s.t.Helper() 94 | 95 | require.NoError(s.t, s.Invoke(f, opts...), "failed to invoke") 96 | } 97 | 98 | // RequireDecorate decorates the scope using the given function, 99 | // halting the test if it fails. 100 | func (c *Container) RequireDecorate(f interface{}, opts ...dig.DecorateOption) { 101 | c.t.Helper() 102 | 103 | require.NoError(c.t, c.Decorate(f, opts...), "failed to decorate") 104 | } 105 | 106 | // RequireDecorate decorates the scope using the given function, 107 | // halting the test if it fails. 108 | func (s *Scope) RequireDecorate(f interface{}, opts ...dig.DecorateOption) { 109 | s.t.Helper() 110 | 111 | require.NoError(s.t, s.Decorate(f, opts...), "failed to decorate") 112 | } 113 | 114 | // Scope builds a subscope of this container with the given name. 115 | // The returned Scope is similarly augmented to ease testing. 116 | func (c *Container) Scope(name string, opts ...dig.ScopeOption) *Scope { 117 | return &Scope{ 118 | scope: c.Container.Scope(name, opts...), 119 | t: c.t, 120 | } 121 | } 122 | 123 | // Scope builds a subscope of this scope with the given name. 124 | // The returned Scope is similarly augmented to ease testing. 125 | func (s *Scope) Scope(name string, opts ...dig.ScopeOption) *Scope { 126 | return &Scope{ 127 | scope: s.scope.Scope(name, opts...), 128 | t: s.t, 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /internal/digtest/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // Package digtest provides utilities used by dig internally to test its own 22 | // functionality. 23 | package digtest 24 | -------------------------------------------------------------------------------- /internal/dot/README.md: -------------------------------------------------------------------------------- 1 | # Dot 2 | 3 | The dot module generates a DOT file representation of a dependency graph. 4 | 5 | ## Interpreting the graph 6 | 7 | The graph should be read from left to right. The leftmost node in the graph (the root node) depends 8 | on its dependency tree to the right. An arrow from node_a to node_b in the graph means that node_b 9 | is consumed by node_a and that node_b is a parameter of node_a. The rendered graph holds the 10 | following kinds of nodes, 11 | 12 | **Nodes:** 13 | 14 | - *Constructors* [Rectangles]: Takes parameters and produces results. 15 | - *Results* [Ovals]: Results inside a constructor are produced by that constructor. Results are consumed 16 | directly by other constructors and/or part of a group of results. 17 | - *Groups* [Diamonds]: Represent value groups in [fx](https://godoc.org/go.uber.org/fx). Multiple results can form a group. Any 18 | result linked to a group by an edge are members of that group. A group is a collection of results. 19 | Groups can also be parameters of constructors. 20 | 21 | **Edges:** 22 | 23 | - *Solid Arrows*: An arrow from node_a to node_b means that node_b is a parameter of node_a and that 24 | node_a depends on node_b. 25 | - *Dashed Arrows*: A dashed arrow from node_a to node_b represents an optional dependency that node_a 26 | has on node_b. 27 | 28 | **Graph Colors:** 29 | 30 | - *Red*: Graph nodes are the root cause failures. 31 | - *Orange*: Graph nodes are the transitive failures. 32 | 33 | ## Testing and verifying changes 34 | 35 | Unit tests and visualize golden tests are run with 36 | 37 | ```shell 38 | $ make test 39 | ``` 40 | 41 | You can visualize the effect of your code changes by visualizing generated test graphs as pngs. 42 | 43 | In the dig root directory, generate the graph DOT files with respect to your latest code changes. 44 | 45 | ```shell 46 | $ go test -generate 47 | ``` 48 | 49 | Assuming that you have [graphviz](https://www.graphviz.org/) installed and are in the testdata directory, 50 | generate a png image representation of a graph for viewing. 51 | 52 | ```shell 53 | $ dot -Tpng ${name_of_dot_file_in_testdata}.dot -o ${name_of_dot_file_in_testdata}.png 54 | $ open ${name_of_dot_file_in_testdata}.png 55 | ``` 56 | 57 | ## Graph Pruning 58 | 59 | If dot.Visualize is used to visualize an error graph, non-failing nodes are pruned out of the graph 60 | to make the error graph more readable to the user. Pruning increases readability since successful 61 | nodes clutter the graph and do not help the user debug errors. 62 | -------------------------------------------------------------------------------- /internal/graph/graph.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package graph 22 | 23 | // Graph represents a simple interface for representation 24 | // of a directed graph. 25 | // It is assumed that each node in the graph is uniquely 26 | // identified with an incremental positive integer (i.e. 1, 2, 3...). 27 | // A value of 0 for a node represents a sentinel error value. 28 | type Graph interface { 29 | // Order returns the total number of nodes in the graph 30 | Order() int 31 | 32 | // EdgesFrom returns a list of integers that each 33 | // represents a node that has an edge from node u. 34 | EdgesFrom(u int) []int 35 | } 36 | 37 | // IsAcyclic uses depth-first search to find cycles 38 | // in a generic graph represented by Graph interface. 39 | // If a cycle is found, it returns a list of nodes that 40 | // are in the cyclic path, identified by their orders. 41 | func IsAcyclic(g Graph) (bool, []int) { 42 | // cycleStart is a node that introduces a cycle in 43 | // the graph. Values in the range [1, g.Order()) mean 44 | // that there exists a cycle in g. 45 | info := newCycleInfo(g.Order()) 46 | 47 | for i := 0; i < g.Order(); i++ { 48 | info.Reset() 49 | 50 | cycle := isAcyclic(g, i, info, nil /* cycle path */) 51 | if len(cycle) > 0 { 52 | return false, cycle 53 | } 54 | } 55 | 56 | return true, nil 57 | } 58 | 59 | // isAcyclic traverses the given graph starting from a specific node 60 | // using depth-first search using recursion. If a cycle is detected, 61 | // it returns the node that contains the "last" edge that introduces 62 | // a cycle. 63 | // For example, running isAcyclic starting from 1 on the following 64 | // graph will return 3. 65 | // 66 | // 1 -> 2 -> 3 -> 1 67 | func isAcyclic(g Graph, u int, info cycleInfo, path []int) []int { 68 | // We've already verified that there are no cycles from this node. 69 | if info[u].Visited { 70 | return nil 71 | } 72 | info[u].Visited = true 73 | info[u].OnStack = true 74 | 75 | path = append(path, u) 76 | for _, v := range g.EdgesFrom(u) { 77 | if !info[v].Visited { 78 | if cycle := isAcyclic(g, v, info, path); len(cycle) > 0 { 79 | return cycle 80 | } 81 | } else if info[v].OnStack { 82 | // We've found a cycle, and we have a full path back. 83 | // Prune it down to just the cyclic nodes. 84 | cycle := path 85 | for i := len(cycle) - 1; i >= 0; i-- { 86 | if cycle[i] == v { 87 | cycle = cycle[i:] 88 | break 89 | } 90 | } 91 | 92 | // Complete the cycle by adding this node to it. 93 | return append(cycle, v) 94 | } 95 | } 96 | info[u].OnStack = false 97 | return nil 98 | } 99 | 100 | // cycleNode keeps track of a single node's info for cycle detection. 101 | type cycleNode struct { 102 | Visited bool 103 | OnStack bool 104 | } 105 | 106 | // cycleInfo contains information about each node while we're trying to find 107 | // cycles. 108 | type cycleInfo []cycleNode 109 | 110 | func newCycleInfo(order int) cycleInfo { 111 | return make(cycleInfo, order) 112 | } 113 | 114 | func (info cycleInfo) Reset() { 115 | for i := range info { 116 | info[i].OnStack = false 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /internal/graph/graph_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package graph 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | type TestGraph struct { 30 | Nodes map[int][]int 31 | } 32 | 33 | func newTestGraph() *TestGraph { 34 | return &TestGraph{ 35 | Nodes: make(map[int][]int), 36 | } 37 | } 38 | 39 | func (g TestGraph) Order() int { 40 | return len(g.Nodes) 41 | } 42 | 43 | func (g TestGraph) EdgesFrom(u int) []int { 44 | return g.Nodes[u] 45 | } 46 | 47 | func TestGraphIsAcyclic(t *testing.T) { 48 | testCases := []struct { 49 | edges [][]int 50 | }{ 51 | // 0 52 | { 53 | // Edges is an adjacency list representation of 54 | // a directed graph. i.e. edges[u] is a list of 55 | // nodes that node u has edges pointing to. 56 | edges: [][]int{ 57 | nil, 58 | }, 59 | }, 60 | // 0 --> 1 --> 2 61 | { 62 | edges: [][]int{ 63 | {1}, 64 | {2}, 65 | nil, 66 | }, 67 | }, 68 | // 0 ---> 1 -------> 2 69 | // | ^ 70 | // '-----------------' 71 | { 72 | edges: [][]int{ 73 | {1, 2}, 74 | {2}, 75 | nil, 76 | }, 77 | }, 78 | // 0 --> 1 --> 2 4 --> 5 79 | // | ^ ^ 80 | // +-----------' | 81 | // '---------> 3 ---' 82 | { 83 | edges: [][]int{ 84 | {1, 2, 3}, 85 | {2}, 86 | nil, 87 | {4}, 88 | {5}, 89 | nil, 90 | }, 91 | }, 92 | } 93 | for _, tt := range testCases { 94 | g := newTestGraph() 95 | for i, neighbors := range tt.edges { 96 | g.Nodes[i] = neighbors 97 | } 98 | ok, cycle := IsAcyclic(g) 99 | assert.True(t, ok, "expected acyclic, got cycle %v", cycle) 100 | } 101 | } 102 | 103 | func TestGraphIsCyclic(t *testing.T) { 104 | testCases := []struct { 105 | edges [][]int 106 | cycle []int 107 | }{ 108 | // 109 | // 0 ---> 1 ---> 2 ---> 3 110 | // ^ | 111 | // '--------------------' 112 | { 113 | edges: [][]int{ 114 | {1}, 115 | {2}, 116 | {3}, 117 | {0}, 118 | }, 119 | cycle: []int{0, 1, 2, 3, 0}, 120 | }, 121 | // 122 | // 0 ---> 1 ---> 2 123 | // ^ | 124 | // '------' 125 | { 126 | edges: [][]int{ 127 | {1}, 128 | {2}, 129 | {1}, 130 | }, 131 | cycle: []int{1, 2, 1}, 132 | }, 133 | // 134 | // 0 ---> 1 ---> 2 ----> 3 135 | // | ^ | ^ 136 | // | '------' | 137 | // '---------------------' 138 | { 139 | edges: [][]int{ 140 | {1, 3}, 141 | {2}, 142 | {1, 3}, 143 | nil, 144 | }, 145 | cycle: []int{1, 2, 1}, 146 | }, 147 | } 148 | for _, tt := range testCases { 149 | g := newTestGraph() 150 | for i, neighbors := range tt.edges { 151 | g.Nodes[i] = neighbors 152 | } 153 | ok, c := IsAcyclic(g) 154 | assert.False(t, ok) 155 | assert.Equal(t, tt.cycle, c) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /invoke.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "fmt" 25 | "reflect" 26 | 27 | "go.uber.org/dig/internal/digreflect" 28 | "go.uber.org/dig/internal/graph" 29 | ) 30 | 31 | // An InvokeOption modifies the default behavior of Invoke. 32 | type InvokeOption interface { 33 | applyInvokeOption(*invokeOptions) 34 | } 35 | 36 | type invokeOptions struct { 37 | Info *InvokeInfo 38 | } 39 | 40 | // InvokeInfo provides information about an Invoke. 41 | type InvokeInfo struct { 42 | Inputs []*Input 43 | } 44 | 45 | // FillInvokeInfo is an InvokeOption that writes information on the types 46 | // accepted by the Invoke function into the specified InvokeInfo. 47 | // For example: 48 | // 49 | // var info dig.InvokeInfo 50 | // err := c.Invoke(func(string, int){}, dig.FillInvokeInfo(&info)) 51 | // 52 | // info.Inputs[0].String() will be string. 53 | // info.Inputs[1].String() will be int. 54 | func FillInvokeInfo(info *InvokeInfo) InvokeOption { 55 | return fillInvokeInfoOption{info: info} 56 | } 57 | 58 | type fillInvokeInfoOption struct { 59 | info *InvokeInfo 60 | } 61 | 62 | func (o fillInvokeInfoOption) String() string { 63 | return fmt.Sprintf("FillInvokeInfo(%p)", o.info) 64 | } 65 | 66 | func (o fillInvokeInfoOption) applyInvokeOption(opts *invokeOptions) { 67 | opts.Info = o.info 68 | } 69 | 70 | // Invoke runs the given function after instantiating its dependencies. 71 | // 72 | // Any arguments that the function has are treated as its dependencies. The 73 | // dependencies are instantiated in an unspecified order along with any 74 | // dependencies that they might have. 75 | // 76 | // The function may return an error to indicate failure. The error will be 77 | // returned to the caller as-is. 78 | // 79 | // If the [RecoverFromPanics] option was given to the container and a panic 80 | // occurs when invoking, a [PanicError] with the panic contained will be 81 | // returned. See [PanicError] for more info. 82 | func (c *Container) Invoke(function interface{}, opts ...InvokeOption) error { 83 | return c.scope.Invoke(function, opts...) 84 | } 85 | 86 | // Invoke runs the given function after instantiating its dependencies. 87 | // 88 | // Any arguments that the function has are treated as its dependencies. The 89 | // dependencies are instantiated in an unspecified order along with any 90 | // dependencies that they might have. 91 | // 92 | // The function may return an error to indicate failure. The error will be 93 | // returned to the caller as-is. 94 | func (s *Scope) Invoke(function interface{}, opts ...InvokeOption) (err error) { 95 | ftype := reflect.TypeOf(function) 96 | if ftype == nil { 97 | return newErrInvalidInput("can't invoke an untyped nil", nil) 98 | } 99 | if ftype.Kind() != reflect.Func { 100 | return newErrInvalidInput( 101 | fmt.Sprintf("can't invoke non-function %v (type %v)", function, ftype), nil) 102 | } 103 | 104 | pl, err := newParamList(ftype, s) 105 | if err != nil { 106 | return err 107 | } 108 | 109 | if err := shallowCheckDependencies(s, pl); err != nil { 110 | return errMissingDependencies{ 111 | Func: digreflect.InspectFunc(function), 112 | Reason: err, 113 | } 114 | } 115 | 116 | if !s.isVerifiedAcyclic { 117 | if ok, cycle := graph.IsAcyclic(s.gh); !ok { 118 | return newErrInvalidInput("cycle detected in dependency graph", s.cycleDetectedError(cycle)) 119 | } 120 | s.isVerifiedAcyclic = true 121 | } 122 | 123 | args, err := pl.BuildList(s) 124 | if err != nil { 125 | return errArgumentsFailed{ 126 | Func: digreflect.InspectFunc(function), 127 | Reason: err, 128 | } 129 | } 130 | if s.recoverFromPanics { 131 | defer func() { 132 | if p := recover(); p != nil { 133 | err = PanicError{ 134 | fn: digreflect.InspectFunc(function), 135 | Panic: p, 136 | } 137 | } 138 | }() 139 | } 140 | 141 | var options invokeOptions 142 | for _, o := range opts { 143 | o.applyInvokeOption(&options) 144 | } 145 | 146 | // Record info for the invoke if requested 147 | if info := options.Info; info != nil { 148 | params := pl.DotParam() 149 | info.Inputs = make([]*Input, len(params)) 150 | for i, p := range params { 151 | info.Inputs[i] = &Input{ 152 | t: p.Type, 153 | optional: p.Optional, 154 | name: p.Name, 155 | group: p.Group, 156 | } 157 | } 158 | 159 | } 160 | 161 | returned := s.invokerFn(reflect.ValueOf(function), args) 162 | if len(returned) == 0 { 163 | return nil 164 | } 165 | if last := returned[len(returned)-1]; isError(last.Type()) { 166 | if err, _ := last.Interface().(error); err != nil { 167 | return err 168 | } 169 | } 170 | 171 | return nil 172 | } 173 | 174 | // Checks that all direct dependencies of the provided parameters are present in 175 | // the container. Returns an error if not. 176 | func shallowCheckDependencies(c containerStore, pl paramList) error { 177 | var err errMissingTypes 178 | 179 | missingDeps := findMissingDependencies(c, pl.Params...) 180 | for _, dep := range missingDeps { 181 | err = append(err, newErrMissingTypes(c, key{name: dep.Name, t: dep.Type})...) 182 | } 183 | 184 | if len(err) > 0 { 185 | return err 186 | } 187 | return nil 188 | } 189 | 190 | func findMissingDependencies(c containerStore, params ...param) []paramSingle { 191 | var missingDeps []paramSingle 192 | 193 | for _, param := range params { 194 | switch p := param.(type) { 195 | case paramSingle: 196 | allProviders := c.getAllValueProviders(p.Name, p.Type) 197 | _, hasDecoratedValue := c.getDecoratedValue(p.Name, p.Type) 198 | // This means that there is no provider that provides this value, 199 | // and it is NOT being decorated and is NOT optional. 200 | // In the case that there is no providers but there is a decorated value 201 | // of this type, it can be provided safely so we can safely skip this. 202 | if len(allProviders) == 0 && !hasDecoratedValue && !p.Optional { 203 | missingDeps = append(missingDeps, p) 204 | } 205 | case paramObject: 206 | for _, f := range p.Fields { 207 | missingDeps = append(missingDeps, findMissingDependencies(c, f.Param)...) 208 | } 209 | } 210 | } 211 | return missingDeps 212 | } 213 | -------------------------------------------------------------------------------- /param_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "io" 25 | "reflect" 26 | "testing" 27 | 28 | "github.com/stretchr/testify/assert" 29 | "github.com/stretchr/testify/require" 30 | ) 31 | 32 | func TestParamListBuild(t *testing.T) { 33 | p, err := newParamList(reflect.TypeOf(func() io.Writer { return nil }), newScope()) 34 | require.NoError(t, err) 35 | assert.Panics(t, func() { 36 | p.Build(newScope()) 37 | }) 38 | } 39 | 40 | func TestParamObjectSuccess(t *testing.T) { 41 | type type1 struct{} 42 | type type2 struct{} 43 | type type3 struct{} 44 | 45 | type in struct { 46 | In 47 | 48 | T1 type1 49 | T2 type2 `optional:"true"` 50 | T3 type3 `name:"foo"` 51 | 52 | Nested struct { 53 | In 54 | 55 | A string 56 | B int32 57 | } `name:"bar"` 58 | } 59 | 60 | po, err := newParamObject(reflect.TypeOf(in{}), newScope()) 61 | require.NoError(t, err) 62 | 63 | require.Len(t, po.Fields, 4) 64 | 65 | t.Run("no tags", func(t *testing.T) { 66 | require.Equal(t, "T1", po.Fields[0].FieldName) 67 | t1, ok := po.Fields[0].Param.(paramSingle) 68 | require.True(t, ok, "T1 must be a paramSingle") 69 | assert.Empty(t, t1.Name) 70 | assert.False(t, t1.Optional) 71 | }) 72 | 73 | t.Run("optional field", func(t *testing.T) { 74 | require.Equal(t, "T2", po.Fields[1].FieldName) 75 | 76 | t2, ok := po.Fields[1].Param.(paramSingle) 77 | require.True(t, ok, "T2 must be a paramSingle") 78 | assert.Empty(t, t2.Name) 79 | assert.True(t, t2.Optional) 80 | }) 81 | 82 | t.Run("named value", func(t *testing.T) { 83 | require.Equal(t, "T3", po.Fields[2].FieldName) 84 | t3, ok := po.Fields[2].Param.(paramSingle) 85 | require.True(t, ok, "T3 must be a paramSingle") 86 | assert.Equal(t, "foo", t3.Name) 87 | assert.False(t, t3.Optional) 88 | }) 89 | 90 | t.Run("tags don't apply to nested dig.In", func(t *testing.T) { 91 | require.Equal(t, "Nested", po.Fields[3].FieldName) 92 | nested, ok := po.Fields[3].Param.(paramObject) 93 | require.True(t, ok, "Nested must be a paramObject") 94 | 95 | assert.Len(t, nested.Fields, 2) 96 | a, ok := nested.Fields[0].Param.(paramSingle) 97 | require.True(t, ok, "Nested.A must be a paramSingle") 98 | assert.Empty(t, a.Name, "Nested.A must not have a name") 99 | }) 100 | } 101 | 102 | func TestParamObjectWithUnexportedFieldsSuccess(t *testing.T) { 103 | type type1 struct{} 104 | type type2 struct{} 105 | 106 | type in struct { 107 | In `ignore-unexported:"true"` 108 | 109 | T1 type1 110 | t2 type2 111 | } 112 | 113 | _ = in{}.t2 // unused 114 | 115 | po, err := newParamObject(reflect.TypeOf(in{}), newScope()) 116 | require.NoError(t, err) 117 | 118 | require.Len(t, po.Fields, 1) 119 | 120 | require.Equal(t, "T1", po.Fields[0].FieldName) 121 | t1, ok := po.Fields[0].Param.(paramSingle) 122 | require.True(t, ok, "T1 must be a paramSingle") 123 | assert.Empty(t, t1.Name) 124 | assert.False(t, t1.Optional) 125 | } 126 | 127 | func TestParamObjectFailure(t *testing.T) { 128 | t.Run("unexported field gets an error", func(t *testing.T) { 129 | type A struct{} 130 | type in struct { 131 | In 132 | 133 | A1 A 134 | a2 A 135 | } 136 | 137 | _ = in{}.a2 // unused but needed 138 | 139 | _, err := newParamObject(reflect.TypeOf(in{}), newScope()) 140 | require.Error(t, err) 141 | assert.Contains(t, err.Error(), 142 | `bad field "a2" of dig.in: unexported fields not allowed in dig.In, did you mean to export "a2" (dig.A)`) 143 | }) 144 | 145 | t.Run("unexported field with empty tag value gets an error", func(t *testing.T) { 146 | type A struct{} 147 | type in struct { 148 | In `ignore-unexported:""` 149 | 150 | A1 A 151 | a2 A 152 | } 153 | 154 | _ = in{}.a2 // unused but needed 155 | 156 | _, err := newParamObject(reflect.TypeOf(in{}), newScope()) 157 | require.Error(t, err) 158 | assert.Contains(t, err.Error(), 159 | `bad field "a2" of dig.in: unexported fields not allowed in dig.In, did you mean to export "a2" (dig.A)`) 160 | }) 161 | 162 | t.Run("unexported field with invalid tag value gets an error", func(t *testing.T) { 163 | type A struct{} 164 | type in struct { 165 | In `ignore-unexported:"foo"` 166 | 167 | A1 A 168 | a2 A 169 | } 170 | 171 | _ = in{}.a2 // unused but needed 172 | 173 | _, err := newParamObject(reflect.TypeOf(in{}), newScope()) 174 | require.Error(t, err) 175 | assert.Contains(t, err.Error(), 176 | `invalid value "foo" for "ignore-unexported" tag on field In: strconv.ParseBool: parsing "foo": invalid syntax`) 177 | }) 178 | } 179 | 180 | func TestParamGroupSliceErrors(t *testing.T) { 181 | tests := []struct { 182 | desc string 183 | shape interface{} 184 | wantErr string 185 | }{ 186 | { 187 | desc: "non-slice type are disallowed", 188 | shape: struct { 189 | In 190 | 191 | Foo string `group:"foo"` 192 | }{}, 193 | wantErr: "value groups may be consumed as slices only: " + 194 | `field "Foo" (string) is not a slice`, 195 | }, 196 | { 197 | desc: "cannot provide name for a group", 198 | shape: struct { 199 | In 200 | 201 | Foo []string `group:"foo" name:"bar"` 202 | }{}, 203 | wantErr: "cannot use named values with value groups: " + 204 | `name:"bar" requested with group:"foo"`, 205 | }, 206 | { 207 | desc: "cannot be optional", 208 | shape: struct { 209 | In 210 | 211 | Foo []string `group:"foo" optional:"true"` 212 | }{}, 213 | wantErr: "value groups cannot be optional", 214 | }, 215 | { 216 | desc: "no flatten in In", 217 | shape: struct { 218 | In 219 | 220 | Foo []string `group:"foo,flatten"` 221 | }{}, 222 | wantErr: "cannot use flatten in parameter value groups", 223 | }, 224 | } 225 | 226 | for _, tt := range tests { 227 | t.Run(tt.desc, func(t *testing.T) { 228 | _, err := newParamObject(reflect.TypeOf(tt.shape), newScope()) 229 | require.Error(t, err, "expected failure") 230 | assert.Contains(t, err.Error(), tt.wantErr) 231 | }) 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /provide_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "fmt" 25 | "io" 26 | "reflect" 27 | "testing" 28 | 29 | "github.com/stretchr/testify/assert" 30 | ) 31 | 32 | func TestProvideOptionStrings(t *testing.T) { 33 | t.Parallel() 34 | 35 | tests := []struct { 36 | desc string 37 | give ProvideOption 38 | want string 39 | }{ 40 | { 41 | desc: "Name", 42 | give: Name("foo"), 43 | want: `Name("foo")`, 44 | }, 45 | { 46 | desc: "Group", 47 | give: Group("bar"), 48 | want: `Group("bar")`, 49 | }, 50 | { 51 | desc: "As", 52 | give: As(new(io.Reader), new(io.Writer)), 53 | want: `As(io.Reader, io.Writer)`, 54 | }, 55 | } 56 | 57 | for _, tt := range tests { 58 | tt := tt 59 | t.Run(tt.desc, func(t *testing.T) { 60 | t.Parallel() 61 | 62 | assert.Equal(t, tt.want, fmt.Sprint(tt.give)) 63 | }) 64 | } 65 | } 66 | 67 | func TestFillProvideInfoString(t *testing.T) { 68 | t.Parallel() 69 | 70 | t.Run("nil", func(t *testing.T) { 71 | t.Parallel() 72 | 73 | assert.Equal(t, "FillProvideInfo(0x0)", fmt.Sprint(FillProvideInfo(nil))) 74 | }) 75 | 76 | t.Run("not nil", func(t *testing.T) { 77 | t.Parallel() 78 | 79 | opt := FillProvideInfo(new(ProvideInfo)) 80 | assert.NotEqual(t, fmt.Sprint(opt), "FillProvideInfo(0x0)") 81 | assert.Contains(t, fmt.Sprint(opt), "FillProvideInfo(0x") 82 | }) 83 | } 84 | 85 | func TestLocationForPCString(t *testing.T) { 86 | opt := LocationForPC(reflect.ValueOf(func() {}).Pointer()) 87 | assert.Contains(t, fmt.Sprint(opt), `LocationForPC("go.uber.org/dig".TestLocationForPCString.func1 `) 88 | } 89 | 90 | func TestExportString(t *testing.T) { 91 | assert.Equal(t, fmt.Sprint(Export(true)), "Export(true)") 92 | assert.Equal(t, fmt.Sprint(Export(false)), "Export(false)") 93 | } 94 | -------------------------------------------------------------------------------- /result_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "fmt" 25 | "io" 26 | "reflect" 27 | "testing" 28 | 29 | "github.com/stretchr/testify/assert" 30 | "github.com/stretchr/testify/require" 31 | ) 32 | 33 | func TestNewResultListErrors(t *testing.T) { 34 | tests := []struct { 35 | desc string 36 | give interface{} 37 | wantError string 38 | }{ 39 | { 40 | desc: "returns dig.In", 41 | give: func() struct{ In } { panic("invalid") }, 42 | wantError: "struct { dig.In } embeds a dig.In", 43 | }, 44 | { 45 | desc: "returns dig.Out+dig.In", 46 | give: func() struct { 47 | Out 48 | In 49 | } { 50 | panic("invalid") 51 | }, 52 | wantError: "struct { dig.Out; dig.In } embeds a dig.In", 53 | }, 54 | } 55 | 56 | for _, tt := range tests { 57 | t.Run(tt.desc, func(t *testing.T) { 58 | _, err := newResultList(reflect.TypeOf(tt.give), resultOptions{}) 59 | require.Error(t, err) 60 | AssertErrorMatches(t, err, 61 | "bad result 1:", 62 | `cannot provide parameter objects: `+tt.wantError) 63 | }) 64 | } 65 | } 66 | 67 | func TestResultListExtractFails(t *testing.T) { 68 | rl, err := newResultList(reflect.TypeOf(func() (io.Writer, error) { 69 | panic("function should not be called") 70 | }), resultOptions{}) 71 | require.NoError(t, err) 72 | assert.Panics(t, func() { 73 | rl.Extract(newStagingContainerWriter(), false, reflect.ValueOf("irrelevant")) 74 | }) 75 | } 76 | 77 | func TestNewResultErrors(t *testing.T) { 78 | type outPtr struct{ *Out } 79 | type out struct{ Out } 80 | type in struct{ In } 81 | type inOut struct { 82 | In 83 | Out 84 | } 85 | 86 | tests := []struct { 87 | give interface{} 88 | err string 89 | }{ 90 | { 91 | give: outPtr{}, 92 | err: "cannot build a result object by embedding *dig.Out, embed dig.Out instead: dig.outPtr embeds *dig.Out", 93 | }, 94 | { 95 | give: (*out)(nil), 96 | err: "cannot return a pointer to a result object, use a value instead: *dig.out is a pointer to a struct that embeds dig.Out", 97 | }, 98 | { 99 | give: in{}, 100 | err: "cannot provide parameter objects: dig.in embeds a dig.In", 101 | }, 102 | { 103 | give: inOut{}, 104 | err: "cannot provide parameter objects: dig.inOut embeds a dig.In", 105 | }, 106 | } 107 | 108 | for _, tt := range tests { 109 | give := reflect.TypeOf(tt.give) 110 | t.Run(fmt.Sprint(give), func(t *testing.T) { 111 | _, err := newResult(give, resultOptions{}) 112 | require.Error(t, err) 113 | assert.Contains(t, err.Error(), tt.err) 114 | }) 115 | } 116 | } 117 | 118 | func TestNewResultObject(t *testing.T) { 119 | typeOfReader := reflect.TypeOf((*io.Reader)(nil)).Elem() 120 | typeOfWriter := reflect.TypeOf((*io.Writer)(nil)).Elem() 121 | 122 | tests := []struct { 123 | desc string 124 | give interface{} 125 | opts resultOptions 126 | 127 | wantFields []resultObjectField 128 | }{ 129 | {desc: "empty", give: struct{ Out }{}}, 130 | { 131 | desc: "multiple values", 132 | give: struct { 133 | Out 134 | 135 | Reader io.Reader 136 | Writer io.Writer 137 | }{}, 138 | wantFields: []resultObjectField{ 139 | { 140 | FieldName: "Reader", 141 | FieldIndex: 1, 142 | Result: resultSingle{Type: typeOfReader}, 143 | }, 144 | { 145 | FieldName: "Writer", 146 | FieldIndex: 2, 147 | Result: resultSingle{Type: typeOfWriter}, 148 | }, 149 | }, 150 | }, 151 | { 152 | desc: "name tag", 153 | give: struct { 154 | Out 155 | 156 | A io.Writer `name:"stream-a"` 157 | B io.Writer `name:"stream-b" ` 158 | }{}, 159 | wantFields: []resultObjectField{ 160 | { 161 | FieldName: "A", 162 | FieldIndex: 1, 163 | Result: resultSingle{Name: "stream-a", Type: typeOfWriter}, 164 | }, 165 | { 166 | FieldName: "B", 167 | FieldIndex: 2, 168 | Result: resultSingle{Name: "stream-b", Type: typeOfWriter}, 169 | }, 170 | }, 171 | }, 172 | { 173 | desc: "group tag", 174 | give: struct { 175 | Out 176 | 177 | Writer io.Writer `group:"writers"` 178 | }{}, 179 | wantFields: []resultObjectField{ 180 | { 181 | FieldName: "Writer", 182 | FieldIndex: 1, 183 | Result: resultGrouped{Group: "writers", Type: typeOfWriter}, 184 | }, 185 | }, 186 | }, 187 | } 188 | 189 | for _, tt := range tests { 190 | t.Run(tt.desc, func(t *testing.T) { 191 | got, err := newResultObject(reflect.TypeOf(tt.give), tt.opts) 192 | require.NoError(t, err) 193 | assert.Equal(t, tt.wantFields, got.Fields) 194 | }) 195 | } 196 | } 197 | 198 | func TestNewResultObjectErrors(t *testing.T) { 199 | tests := []struct { 200 | desc string 201 | give interface{} 202 | opts resultOptions 203 | err string 204 | }{ 205 | { 206 | desc: "unexported fields", 207 | give: struct { 208 | Out 209 | 210 | writer io.Writer 211 | }{}, 212 | err: `unexported fields not allowed in dig.Out, did you mean to export "writer" (io.Writer)`, 213 | }, 214 | { 215 | desc: "error field", 216 | give: struct { 217 | Out 218 | 219 | Error error 220 | }{}, 221 | err: `bad field "Error" of struct { dig.Out; Error error }: cannot return an error here, return it from the constructor instead`, 222 | }, 223 | { 224 | desc: "nested dig.In", 225 | give: struct { 226 | Out 227 | 228 | Nested struct{ In } 229 | }{}, 230 | err: `bad field "Nested"`, 231 | }, 232 | { 233 | desc: "group with name should fail", 234 | give: struct { 235 | Out 236 | 237 | Foo string `group:"foo" name:"bar"` 238 | }{}, 239 | err: "cannot use named values with value groups: " + 240 | `name:"bar" provided with group:"foo"`, 241 | }, 242 | { 243 | desc: "group marked as optional", 244 | give: struct { 245 | Out 246 | 247 | Foo string `group:"foo" optional:"true"` 248 | }{}, 249 | err: "value groups cannot be optional", 250 | }, 251 | { 252 | desc: "name option", 253 | give: struct { 254 | Out 255 | 256 | Reader io.Reader 257 | }{}, 258 | opts: resultOptions{Name: "foo"}, 259 | err: `cannot specify a name for result objects`, 260 | }, 261 | { 262 | desc: "name option with name tag", 263 | give: struct { 264 | Out 265 | 266 | A io.Writer `name:"stream-a"` 267 | B io.Writer 268 | }{}, 269 | opts: resultOptions{Name: "stream"}, 270 | err: `cannot specify a name for result objects`, 271 | }, 272 | { 273 | desc: "group tag with name option", 274 | give: struct { 275 | Out 276 | 277 | Reader io.Reader 278 | Writer io.Writer `group:"writers"` 279 | }{}, 280 | opts: resultOptions{Name: "foo"}, 281 | err: `cannot specify a name for result objects`, 282 | }, 283 | { 284 | desc: "flatten on non-slice", 285 | give: struct { 286 | Out 287 | 288 | Writer io.Writer `group:"writers,flatten"` 289 | }{}, 290 | err: "flatten can be applied to slices only", 291 | }, 292 | { 293 | desc: "soft on value group", 294 | give: struct { 295 | Out 296 | 297 | Fries []struct{} `group:"potato,flatten,soft"` 298 | }{}, 299 | err: "cannot use soft with result value groups", 300 | }, 301 | } 302 | 303 | for _, tt := range tests { 304 | t.Run(tt.desc, func(t *testing.T) { 305 | _, err := newResultObject(reflect.TypeOf(tt.give), tt.opts) 306 | require.Error(t, err) 307 | assert.Contains(t, err.Error(), tt.err) 308 | }) 309 | } 310 | } 311 | 312 | type fakeResultVisit struct { 313 | Visit result 314 | AnnotateWithField *resultObjectField 315 | AnnotateWithPosition int 316 | Return fakeResultVisits 317 | } 318 | 319 | func (fv fakeResultVisit) String() string { 320 | switch { 321 | case fv.Visit != nil: 322 | return fmt.Sprintf("Visit(%#v) -> %v", fv.Visit, fv.Return) 323 | case fv.AnnotateWithField != nil: 324 | return fmt.Sprintf("AnnotateWithField(%#v) -> %v", *fv.AnnotateWithField, fv.Return) 325 | default: 326 | return fmt.Sprintf("AnnotateWithPosition(%v) -> %v", fv.AnnotateWithPosition, fv.Return) 327 | } 328 | } 329 | 330 | type fakeResultVisits []fakeResultVisit 331 | 332 | func (vs fakeResultVisits) Visitor(t *testing.T) resultVisitor { 333 | return &fakeResultVisitor{t: t, visits: vs} 334 | } 335 | 336 | type fakeResultVisitor struct { 337 | t *testing.T 338 | visits fakeResultVisits 339 | } 340 | 341 | func (fv *fakeResultVisitor) popNext(call string) fakeResultVisit { 342 | if len(fv.visits) == 0 { 343 | fv.t.Fatalf("received unexpected call %v: no more calls were expected", call) 344 | } 345 | 346 | visit := fv.visits[0] 347 | fv.visits = fv.visits[1:] 348 | return visit 349 | } 350 | 351 | func (fv *fakeResultVisitor) Visit(r result) resultVisitor { 352 | v := fv.popNext(fmt.Sprintf("Visit(%#v)", r)) 353 | if !reflect.DeepEqual(r, v.Visit) { 354 | fv.t.Fatalf("received unexpected call Visit(%#v)\nexpected %v", r, v) 355 | } 356 | return &fakeResultVisitor{t: fv.t, visits: v.Return} 357 | } 358 | 359 | func (fv *fakeResultVisitor) AnnotateWithField(f resultObjectField) resultVisitor { 360 | v := fv.popNext(fmt.Sprintf("AnnotateWithField(%#v)", f)) 361 | if v.AnnotateWithField == nil || !reflect.DeepEqual(f, *v.AnnotateWithField) { 362 | fv.t.Fatalf("received unexpected call AnnotateWithField(%#v)\nexpected %v", f, v) 363 | } 364 | return &fakeResultVisitor{t: fv.t, visits: v.Return} 365 | } 366 | 367 | func (fv *fakeResultVisitor) AnnotateWithPosition(i int) resultVisitor { 368 | v := fv.popNext(fmt.Sprintf("AnnotateWithPosition(%v)", i)) 369 | if i != v.AnnotateWithPosition { 370 | fv.t.Fatalf("received unexpected call AnnotateWithPosition(%v)\nexpected %v", i, v) 371 | } 372 | return &fakeResultVisitor{t: fv.t, visits: v.Return} 373 | } 374 | 375 | func TestWalkResult(t *testing.T) { 376 | t.Run("invalid result type", func(t *testing.T) { 377 | type badResult struct{ result } 378 | visitor := fakeResultVisits{ 379 | {Visit: badResult{}, Return: fakeResultVisits{}}, 380 | }.Visitor(t) 381 | assert.Panics(t, 382 | func() { 383 | walkResult(badResult{}, visitor) 384 | }) 385 | }) 386 | 387 | t.Run("resultObject ordering", func(t *testing.T) { 388 | type type1 struct{} 389 | type type2 struct{} 390 | type type3 struct{} 391 | type type4 struct{} 392 | 393 | typ := reflect.TypeOf(struct { 394 | Out 395 | 396 | T1 type1 397 | T2 type2 398 | 399 | Nested struct { 400 | Out 401 | 402 | T3 type3 403 | T4 type4 404 | } 405 | }{}) 406 | 407 | ro, err := newResultObject(typ, resultOptions{}) 408 | require.NoError(t, err) 409 | 410 | v := fakeResultVisits{ 411 | { 412 | Visit: ro, 413 | Return: fakeResultVisits{ 414 | { 415 | AnnotateWithField: &ro.Fields[0], 416 | Return: fakeResultVisits{ 417 | {Visit: ro.Fields[0].Result}, 418 | }, 419 | }, 420 | { 421 | AnnotateWithField: &ro.Fields[1], 422 | Return: fakeResultVisits{ 423 | {Visit: ro.Fields[1].Result}, 424 | }, 425 | }, 426 | { 427 | AnnotateWithField: &ro.Fields[2], 428 | Return: fakeResultVisits{ 429 | { 430 | Visit: ro.Fields[2].Result, 431 | Return: fakeResultVisits{ 432 | { 433 | AnnotateWithField: &ro.Fields[2].Result.(resultObject).Fields[0], 434 | Return: fakeResultVisits{ 435 | {Visit: ro.Fields[2].Result.(resultObject).Fields[0].Result}, 436 | }, 437 | }, 438 | { 439 | AnnotateWithField: &ro.Fields[2].Result.(resultObject).Fields[1], 440 | Return: fakeResultVisits{ 441 | {Visit: ro.Fields[2].Result.(resultObject).Fields[1].Result}, 442 | }, 443 | }, 444 | }, 445 | }, 446 | }, 447 | }, 448 | }, 449 | }, 450 | }.Visitor(t) 451 | 452 | walkResult(ro, v) 453 | }) 454 | } 455 | -------------------------------------------------------------------------------- /scope.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "bytes" 25 | "fmt" 26 | "math/rand" 27 | "reflect" 28 | "sort" 29 | "time" 30 | 31 | "go.uber.org/dig/internal/digclock" 32 | ) 33 | 34 | // A ScopeOption modifies the default behavior of Scope; currently, 35 | // there are no implementations. 36 | type ScopeOption interface { 37 | noScopeOption() // yet 38 | } 39 | 40 | // Scope is a scoped DAG of types and their dependencies. 41 | // A Scope may also have one or more child Scopes that inherit 42 | // from it. 43 | type Scope struct { 44 | // This implements containerStore interface. 45 | 46 | // Name of the Scope 47 | name string 48 | // Mapping from key to all the constructor node that can provide a value for that 49 | // key. 50 | providers map[key][]*constructorNode 51 | 52 | // Mapping from key to the decorator that decorates a value for that key. 53 | decorators map[key]*decoratorNode 54 | 55 | // constructorNodes provided directly to this Scope. i.e. it does not include 56 | // any nodes that were provided to the parent Scope this inherited from. 57 | nodes []*constructorNode 58 | 59 | // Values that generated via decorators in the Scope. 60 | decoratedValues map[key]reflect.Value 61 | 62 | // Values that generated directly in the Scope. 63 | values map[key]reflect.Value 64 | 65 | // Values groups that generated directly in the Scope. 66 | groups map[key][]reflect.Value 67 | 68 | // Values groups that generated via decoraters in the Scope. 69 | decoratedGroups map[key]reflect.Value 70 | 71 | // Source of randomness. 72 | rand *rand.Rand 73 | 74 | // Flag indicating whether the graph has been checked for cycles. 75 | isVerifiedAcyclic bool 76 | 77 | // Defer acyclic check on provide until Invoke. 78 | deferAcyclicVerification bool 79 | 80 | // Recover from panics in user-provided code and wrap in an exported error type. 81 | recoverFromPanics bool 82 | 83 | // invokerFn calls a function with arguments provided to Provide or Invoke. 84 | invokerFn invokerFn 85 | 86 | // graph of this Scope. Note that this holds the dependency graph of all the 87 | // nodes that affect this Scope, not just the ones provided directly to this Scope. 88 | gh *graphHolder 89 | 90 | // Parent of this Scope. 91 | parentScope *Scope 92 | 93 | // All the child scopes of this Scope. 94 | childScopes []*Scope 95 | 96 | // clockSrc stores the source of time. Defaults to system clock. 97 | clockSrc digclock.Clock 98 | } 99 | 100 | func newScope() *Scope { 101 | s := &Scope{ 102 | providers: make(map[key][]*constructorNode), 103 | decorators: make(map[key]*decoratorNode), 104 | values: make(map[key]reflect.Value), 105 | decoratedValues: make(map[key]reflect.Value), 106 | groups: make(map[key][]reflect.Value), 107 | decoratedGroups: make(map[key]reflect.Value), 108 | invokerFn: defaultInvoker, 109 | rand: rand.New(rand.NewSource(time.Now().UnixNano())), 110 | clockSrc: digclock.System, 111 | } 112 | s.gh = newGraphHolder(s) 113 | return s 114 | } 115 | 116 | // Scope creates a new Scope with the given name and options from current Scope. 117 | // Any constructors that the current Scope knows about, as well as any modifications 118 | // made to it in the future will be propagated to the child scope. 119 | // However, no modifications made to the child scope being created will be propagated 120 | // to the parent Scope. 121 | func (s *Scope) Scope(name string, opts ...ScopeOption) *Scope { 122 | child := newScope() 123 | child.name = name 124 | child.parentScope = s 125 | child.invokerFn = s.invokerFn 126 | child.clockSrc = s.clockSrc 127 | child.deferAcyclicVerification = s.deferAcyclicVerification 128 | child.recoverFromPanics = s.recoverFromPanics 129 | 130 | // child copies the parent's graph nodes. 131 | for _, node := range s.gh.nodes { 132 | child.gh.nodes = append(child.gh.nodes, node) 133 | if ctrNode, ok := node.Wrapped.(*constructorNode); ok { 134 | ctrNode.CopyOrder(s, child) 135 | } 136 | } 137 | 138 | for _, opt := range opts { 139 | opt.noScopeOption() 140 | } 141 | 142 | s.childScopes = append(s.childScopes, child) 143 | return child 144 | } 145 | 146 | // ancestors returns a list of scopes of ancestors of this scope up to the 147 | // root. The scope at at index 0 is this scope itself. 148 | func (s *Scope) ancestors() []*Scope { 149 | var scopes []*Scope 150 | for s := s; s != nil; s = s.parentScope { 151 | scopes = append(scopes, s) 152 | } 153 | return scopes 154 | } 155 | 156 | func (s *Scope) appendSubscopes(dest []*Scope) []*Scope { 157 | dest = append(dest, s) 158 | for _, cs := range s.childScopes { 159 | dest = cs.appendSubscopes(dest) 160 | } 161 | return dest 162 | } 163 | 164 | func (s *Scope) storesToRoot() []containerStore { 165 | scopes := s.ancestors() 166 | stores := make([]containerStore, len(scopes)) 167 | for i, s := range scopes { 168 | stores[i] = s 169 | } 170 | return stores 171 | } 172 | 173 | func (s *Scope) knownTypes() []reflect.Type { 174 | typeSet := make(map[reflect.Type]struct{}, len(s.providers)) 175 | for k := range s.providers { 176 | typeSet[k.t] = struct{}{} 177 | } 178 | 179 | types := make([]reflect.Type, 0, len(typeSet)) 180 | for t := range typeSet { 181 | types = append(types, t) 182 | } 183 | sort.Sort(byTypeName(types)) 184 | return types 185 | } 186 | 187 | func (s *Scope) getValue(name string, t reflect.Type) (v reflect.Value, ok bool) { 188 | v, ok = s.values[key{name: name, t: t}] 189 | return 190 | } 191 | 192 | func (s *Scope) getDecoratedValue(name string, t reflect.Type) (v reflect.Value, ok bool) { 193 | v, ok = s.decoratedValues[key{name: name, t: t}] 194 | return 195 | } 196 | 197 | func (s *Scope) setValue(name string, t reflect.Type, v reflect.Value) { 198 | s.values[key{name: name, t: t}] = v 199 | } 200 | 201 | func (s *Scope) setDecoratedValue(name string, t reflect.Type, v reflect.Value) { 202 | s.decoratedValues[key{name: name, t: t}] = v 203 | } 204 | 205 | func (s *Scope) getValueGroup(name string, t reflect.Type) []reflect.Value { 206 | items := s.groups[key{group: name, t: t}] 207 | // shuffle the list so users don't rely on the ordering of grouped values 208 | return shuffledCopy(s.rand, items) 209 | } 210 | 211 | func (s *Scope) getDecoratedValueGroup(name string, t reflect.Type) (reflect.Value, bool) { 212 | items, ok := s.decoratedGroups[key{group: name, t: t}] 213 | return items, ok 214 | } 215 | 216 | func (s *Scope) submitGroupedValue(name string, t reflect.Type, v reflect.Value) { 217 | k := key{group: name, t: t} 218 | s.groups[k] = append(s.groups[k], v) 219 | } 220 | 221 | func (s *Scope) submitDecoratedGroupedValue(name string, t reflect.Type, v reflect.Value) { 222 | k := key{group: name, t: t} 223 | s.decoratedGroups[k] = v 224 | } 225 | 226 | func (s *Scope) getValueProviders(name string, t reflect.Type) []provider { 227 | return s.getProviders(key{name: name, t: t}) 228 | } 229 | 230 | func (s *Scope) getGroupProviders(name string, t reflect.Type) []provider { 231 | return s.getProviders(key{group: name, t: t}) 232 | } 233 | 234 | func (s *Scope) getValueDecorator(name string, t reflect.Type) (decorator, bool) { 235 | return s.getDecorators(key{name: name, t: t}) 236 | } 237 | 238 | func (s *Scope) getGroupDecorator(name string, t reflect.Type) (decorator, bool) { 239 | return s.getDecorators(key{group: name, t: t}) 240 | } 241 | 242 | func (s *Scope) getDecorators(k key) (decorator, bool) { 243 | d, found := s.decorators[k] 244 | return d, found 245 | } 246 | 247 | func (s *Scope) getProviders(k key) []provider { 248 | nodes := s.providers[k] 249 | providers := make([]provider, len(nodes)) 250 | for i, n := range nodes { 251 | providers[i] = n 252 | } 253 | return providers 254 | } 255 | 256 | func (s *Scope) getAllGroupProviders(name string, t reflect.Type) []provider { 257 | return s.getAllProviders(key{group: name, t: t}) 258 | } 259 | 260 | func (s *Scope) getAllValueProviders(name string, t reflect.Type) []provider { 261 | return s.getAllProviders(key{name: name, t: t}) 262 | } 263 | 264 | func (s *Scope) getAllProviders(k key) []provider { 265 | allScopes := s.ancestors() 266 | var providers []provider 267 | for _, scope := range allScopes { 268 | providers = append(providers, scope.getProviders(k)...) 269 | } 270 | return providers 271 | } 272 | 273 | func (s *Scope) invoker() invokerFn { 274 | return s.invokerFn 275 | } 276 | 277 | func (s *Scope) clock() digclock.Clock { 278 | return s.clockSrc 279 | } 280 | 281 | // adds a new graphNode to this Scope and all of its descendent 282 | // scope. 283 | func (s *Scope) newGraphNode(wrapped interface{}, orders map[*Scope]int) { 284 | orders[s] = s.gh.NewNode(wrapped) 285 | for _, cs := range s.childScopes { 286 | cs.newGraphNode(wrapped, orders) 287 | } 288 | } 289 | 290 | func (s *Scope) cycleDetectedError(cycle []int) error { 291 | var path []cycleErrPathEntry 292 | for _, n := range cycle { 293 | if n, ok := s.gh.Lookup(n).(*constructorNode); ok { 294 | path = append(path, cycleErrPathEntry{ 295 | Key: key{ 296 | t: n.CType(), 297 | }, 298 | Func: n.Location(), 299 | }) 300 | } 301 | } 302 | return errCycleDetected{Path: path, scope: s} 303 | } 304 | 305 | // Returns the root Scope that can be reached from this Scope. 306 | func (s *Scope) rootScope() *Scope { 307 | curr := s 308 | for curr.parentScope != nil { 309 | curr = curr.parentScope 310 | } 311 | return curr 312 | } 313 | 314 | // String representation of the entire Scope 315 | func (s *Scope) String() string { 316 | b := &bytes.Buffer{} 317 | fmt.Fprintln(b, "nodes: {") 318 | for k, vs := range s.providers { 319 | for _, v := range vs { 320 | fmt.Fprintln(b, "\t", k, "->", v) 321 | } 322 | } 323 | fmt.Fprintln(b, "}") 324 | 325 | fmt.Fprintln(b, "values: {") 326 | for k, v := range s.values { 327 | fmt.Fprintln(b, "\t", k, "=>", v) 328 | } 329 | for k, vs := range s.groups { 330 | for _, v := range vs { 331 | fmt.Fprintln(b, "\t", k, "=>", v) 332 | } 333 | } 334 | fmt.Fprintln(b, "}") 335 | 336 | return b.String() 337 | } 338 | -------------------------------------------------------------------------------- /scope_int_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestScopeAncestorsAndStoresToRoot(t *testing.T) { 30 | c := New() 31 | s1 := c.Scope("child1") 32 | s2 := s1.Scope("child2") 33 | s3 := s2.Scope("child2") 34 | 35 | assert.Equal(t, []containerStore{s3, s2, s1, c.scope}, s3.storesToRoot()) 36 | assert.Equal(t, []*Scope{s3, s2, s1, c.scope}, s3.ancestors()) 37 | } 38 | -------------------------------------------------------------------------------- /scope_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig_test 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | "go.uber.org/dig" 28 | "go.uber.org/dig/internal/digtest" 29 | ) 30 | 31 | func TestScopedOperations(t *testing.T) { 32 | t.Parallel() 33 | 34 | t.Run("private provides", func(t *testing.T) { 35 | c := digtest.New(t) 36 | s := c.Scope("child") 37 | type A struct{} 38 | 39 | f := func(a *A) { 40 | assert.NotEqual(t, nil, a) 41 | } 42 | 43 | s.RequireProvide(func() *A { return &A{} }) 44 | s.RequireInvoke(f) 45 | assert.Error(t, c.Invoke(f)) 46 | }) 47 | 48 | t.Run("private provides inherits", func(t *testing.T) { 49 | type A struct{} 50 | type B struct{} 51 | 52 | useA := func(a *A) { 53 | assert.NotEqual(t, nil, a) 54 | } 55 | useB := func(b *B) { 56 | assert.NotEqual(t, nil, b) 57 | } 58 | 59 | c := digtest.New(t) 60 | c.RequireProvide(func() *A { return &A{} }) 61 | 62 | child := c.Scope("child") 63 | child.RequireProvide(func() *B { return &B{} }) 64 | child.RequireInvoke(useA) 65 | child.RequireInvoke(useB) 66 | 67 | grandchild := child.Scope("grandchild") 68 | 69 | grandchild.RequireInvoke(useA) 70 | grandchild.RequireInvoke(useB) 71 | assert.Error(t, c.Invoke(useB)) 72 | }) 73 | 74 | t.Run("private provides doesn't depend on invoke order", func(t *testing.T) { 75 | c := digtest.New(t) 76 | child := c.Scope("child") 77 | 78 | c.RequireProvide(func() string { return "container" }) 79 | child.RequireProvide(func() string { return "child" }) 80 | 81 | verifyContainerStr := func(s string) { 82 | assert.Equal(t, "container", s) 83 | } 84 | verifyChildStr := func(s string) { 85 | assert.Equal(t, "child", s) 86 | } 87 | 88 | c.RequireInvoke(verifyContainerStr) 89 | child.RequireInvoke(verifyChildStr) 90 | }) 91 | 92 | t.Run("provides to top-level Container propagates to all scopes", func(t *testing.T) { 93 | type A struct{} 94 | 95 | // Scope tree: 96 | // root <-- Provide(func() *A) 97 | // / \ 98 | // c1 c2 99 | // | / \ 100 | // gc1 gc2 gc3 101 | var allScopes []*digtest.Scope 102 | root := digtest.New(t) 103 | 104 | allScopes = append(allScopes, root.Scope("child 1"), root.Scope("child 2")) 105 | allScopes = append(allScopes, allScopes[0].Scope("grandchild 1"), allScopes[1].Scope("grandchild 2"), allScopes[1].Scope("grandchild 3")) 106 | 107 | root.RequireProvide(func() *A { 108 | return &A{} 109 | }) 110 | 111 | // top-level provide should be available in all the scopes. 112 | for _, scope := range allScopes { 113 | scope.RequireInvoke(func(a *A) {}) 114 | } 115 | }) 116 | 117 | t.Run("provide with Export", func(t *testing.T) { 118 | // Scope tree: 119 | // root 120 | // / \ 121 | // c1 c2 122 | // | / \ 123 | // gc1 gc2 gc3 <-- Provide(func() *A) 124 | 125 | root := digtest.New(t) 126 | var allScopes []*digtest.Scope 127 | 128 | allScopes = append(allScopes, root.Scope("child 1"), root.Scope("child 2")) 129 | allScopes = append(allScopes, allScopes[0].Scope("grandchild 1"), allScopes[1].Scope("grandchild 2"), allScopes[1].Scope("grandchild 3")) 130 | 131 | type A struct{} 132 | // provide to the leaf Scope with Export option set. 133 | allScopes[len(allScopes)-1].RequireProvide(func() *A { 134 | return &A{} 135 | }, dig.Export(true)) 136 | 137 | // since constructor was provided with Export option, this should let all the Scopes below should see it. 138 | for _, scope := range allScopes { 139 | scope.RequireInvoke(func(a *A) {}) 140 | } 141 | }) 142 | 143 | t.Run("parent shares values with children", func(t *testing.T) { 144 | type ( 145 | T1 struct{ s string } 146 | T2 struct{} 147 | ) 148 | 149 | parent := digtest.New(t) 150 | 151 | parent.RequireProvide(func() T1 { 152 | assert.Fail(t, "parent should not be called") 153 | return T1{"parent"} 154 | }) 155 | 156 | child := parent.Scope("child") 157 | 158 | var childCalled bool 159 | defer func() { 160 | assert.True(t, childCalled, "child constructor must be called") 161 | }() 162 | child.RequireProvide(func() T1 { 163 | childCalled = true 164 | return T1{"child"} 165 | }) 166 | 167 | child.RequireProvide(func(v T1) T2 { 168 | assert.Equal(t, "child", v.s, 169 | "value should be built by child") 170 | return T2{} 171 | }) 172 | 173 | child.RequireInvoke(func(T2) {}) 174 | }) 175 | } 176 | 177 | func TestScopeFailures(t *testing.T) { 178 | t.Parallel() 179 | 180 | t.Run("introduce a cycle with child", func(t *testing.T) { 181 | // what root sees: 182 | // A <- B C 183 | // | ^ 184 | // |_________| 185 | // 186 | // what child sees: 187 | // A <- B <- C 188 | // | ^ 189 | // |_________| 190 | type A struct{} 191 | type B struct{} 192 | type C struct{} 193 | newA := func(*C) *A { return &A{} } 194 | newB := func(*A) *B { return &B{} } 195 | newC := func(*B) *C { return &C{} } 196 | 197 | // Create a child Scope, and introduce a cycle 198 | // in the child only. 199 | check := func(c *digtest.Container, fails bool) { 200 | s := c.Scope("child") 201 | c.RequireProvide(newA) 202 | s.RequireProvide(newB) 203 | err := c.Provide(newC) 204 | 205 | if fails { 206 | assert.Error(t, err, "expected a cycle to be introduced in the child") 207 | assert.Contains(t, err.Error(), `[scope "child"]`) 208 | } else { 209 | assert.NoError(t, err) 210 | } 211 | } 212 | 213 | // Same as check, but this time child should inherit 214 | // parent-provided constructors upon construction. 215 | checkWithInheritance := func(c *digtest.Container, fails bool) { 216 | c.RequireProvide(newA) 217 | s := c.Scope("child") 218 | s.RequireProvide(newB) 219 | err := c.Provide(newC) 220 | if fails { 221 | assert.Error(t, err, "expected a cycle to be introduced in the child") 222 | assert.Contains(t, err.Error(), `[scope "child"]`) 223 | } else { 224 | assert.NoError(t, err) 225 | } 226 | } 227 | 228 | // Test using different permutations 229 | nodeferContainers := []func() *digtest.Container{ 230 | func() *digtest.Container { return digtest.New(t) }, 231 | func() *digtest.Container { return digtest.New(t, dig.DryRun(true)) }, 232 | func() *digtest.Container { return digtest.New(t, dig.DryRun(false)) }, 233 | } 234 | // Container permutations with DeferAcyclicVerification. 235 | deferredContainers := []func() *digtest.Container{ 236 | func() *digtest.Container { return digtest.New(t, dig.DeferAcyclicVerification()) }, 237 | func() *digtest.Container { return digtest.New(t, dig.DeferAcyclicVerification(), dig.DryRun(true)) }, 238 | func() *digtest.Container { return digtest.New(t, dig.DeferAcyclicVerification(), dig.DryRun(false)) }, 239 | } 240 | 241 | for _, c := range nodeferContainers { 242 | check(c(), true) 243 | checkWithInheritance(c(), true) 244 | } 245 | 246 | // with deferAcyclicVerification, these should not 247 | // error on Provides. 248 | for _, c := range deferredContainers { 249 | check(c(), false) 250 | checkWithInheritance(c(), false) 251 | } 252 | }) 253 | 254 | t.Run("introduce a cycle with Export option", func(t *testing.T) { 255 | // what root and child1 sees: 256 | // A <- B C 257 | // | ^ 258 | // |_________| 259 | // 260 | // what child2 sees: 261 | // A <- B <- C 262 | // | ^ 263 | // |_________| 264 | 265 | type A struct{} 266 | type B struct{} 267 | type C struct{} 268 | newA := func(*C) *A { return &A{} } 269 | newB := func(*A) *B { return &B{} } 270 | newC := func(*B) *C { return &C{} } 271 | 272 | root := digtest.New(t) 273 | child1 := root.Scope("child 1") 274 | child2 := root.Scope("child 2") 275 | 276 | // A <- B made available to all Scopes with root provision. 277 | root.RequireProvide(newA) 278 | 279 | // B <- C made available to only child 2 with private provide. 280 | child2.RequireProvide(newB) 281 | 282 | // C <- A made available to all Scopes with Export provide. 283 | err := child1.Provide(newC, dig.Export(true)) 284 | assert.Error(t, err, "expected a cycle to be introduced in child 2") 285 | assert.Contains(t, err.Error(), `[scope "child 2"]`) 286 | }) 287 | 288 | t.Run("private provides do not propagate upstream", func(t *testing.T) { 289 | type A struct{} 290 | 291 | root := digtest.New(t) 292 | c := root.Scope("child") 293 | gc := c.Scope("grandchild") 294 | gc.RequireProvide(func() *A { return &A{} }) 295 | 296 | assert.Error(t, root.Invoke(func(a *A) {}), "invoking on grandchild's private-provided type should fail") 297 | assert.Error(t, c.Invoke(func(a *A) {}), "invoking on child's private-provided type should fail") 298 | }) 299 | 300 | t.Run("private provides to child should be available to grandchildren, but not root", func(t *testing.T) { 301 | type A struct{} 302 | // Scope tree: 303 | // root 304 | // | 305 | // child <-- Provide(func() *A) 306 | // / \ 307 | // gc1 gc2 308 | root := digtest.New(t) 309 | c := root.Scope("child") 310 | gc := c.Scope("grandchild") 311 | 312 | c.RequireProvide(func() *A { return &A{} }) 313 | 314 | err := root.Invoke(func(a *A) {}) 315 | assert.Error(t, err, "expected Invoke in root container on child's private-provided type to fail") 316 | assert.Contains(t, err.Error(), "missing type: *dig_test.A") 317 | 318 | gc.RequireInvoke(func(a *A) {}) 319 | }) 320 | } 321 | 322 | func TestScopeValueGroups(t *testing.T) { 323 | t.Run("provide in parent and child", func(t *testing.T) { 324 | type result struct { 325 | dig.Out 326 | 327 | Value string `group:"foo"` 328 | } 329 | 330 | root := digtest.New(t) 331 | root.RequireProvide(func() result { 332 | return result{Value: "a"} 333 | }) 334 | 335 | root.RequireProvide(func() result { 336 | return result{Value: "b"} 337 | }) 338 | 339 | root.RequireProvide(func() result { 340 | return result{Value: "c"} 341 | }) 342 | 343 | child := root.Scope("child") 344 | child.RequireProvide(func() result { 345 | return result{Value: "d"} 346 | }) 347 | 348 | type param struct { 349 | dig.In 350 | 351 | Values []string `group:"foo"` 352 | } 353 | 354 | t.Run("invoke parent", func(t *testing.T) { 355 | root.RequireInvoke(func(i param) { 356 | assert.ElementsMatch(t, []string{"a", "b", "c"}, i.Values) 357 | }) 358 | }) 359 | 360 | t.Run("invoke child", func(t *testing.T) { 361 | child.RequireInvoke(func(i param) { 362 | assert.ElementsMatch(t, []string{"a", "b", "c", "d"}, i.Values) 363 | }) 364 | }) 365 | }) 366 | 367 | t.Run("value group as a parent dependency", func(t *testing.T) { 368 | // Tree: 369 | // 370 | // root defines a function that consumes the value group 371 | // | 372 | // | 373 | // child produces values to the value group 374 | 375 | type T1 struct{} 376 | type param struct { 377 | dig.In 378 | 379 | Values []string `group:"foo"` 380 | } 381 | 382 | root := digtest.New(t) 383 | 384 | root.RequireProvide(func(p param) T1 { 385 | assert.ElementsMatch(t, []string{"a", "b", "c"}, p.Values) 386 | return T1{} 387 | }) 388 | 389 | child := root.Scope("child") 390 | child.RequireProvide(func() string { return "a" }, dig.Group("foo"), dig.Export(true)) 391 | child.RequireProvide(func() string { return "b" }, dig.Group("foo"), dig.Export(true)) 392 | child.RequireProvide(func() string { return "c" }, dig.Group("foo"), dig.Export(true)) 393 | 394 | // Invocation in child should see values provided to the child, 395 | // even though the constructor we're invoking is provided in 396 | // the parent. 397 | child.RequireInvoke(func(T1) {}) 398 | }) 399 | } 400 | 401 | // This tests that a child scope correctly copies its parent's graph, 402 | // including information about the order of each node. 403 | // Otherwise, during cycle detection, constructor nodes might 404 | // return 0 as the order for all functions in the root scope, 405 | // causing cycle detection to detect cycles that don't exist. 406 | func TestFalsePositiveScopeCycleDetection(t *testing.T) { 407 | t.Run("single provide", func(t *testing.T) { 408 | root := digtest.New(t) 409 | root.RequireProvide(func(val string) int { return 0 }) 410 | root.RequireProvide(func() string { return "sample" }) 411 | child := root.Scope("child") 412 | // Cycle detection would error here because previous two provides 413 | // would both have order 0 for child scope. 414 | child.RequireProvide(func() float32 { return 0 }) 415 | }) 416 | } 417 | -------------------------------------------------------------------------------- /stringer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig_test 22 | 23 | import ( 24 | "math/rand" 25 | "testing" 26 | 27 | "github.com/stretchr/testify/assert" 28 | "go.uber.org/dig" 29 | "go.uber.org/dig/internal/digtest" 30 | ) 31 | 32 | func TestStringer(t *testing.T) { 33 | type A struct{} 34 | type B struct{} 35 | type C struct{} 36 | type D struct{} 37 | 38 | type in struct { 39 | dig.In 40 | 41 | A A `name:"foo"` 42 | B B `optional:"true"` 43 | C C `name:"bar" optional:"true"` 44 | 45 | Strings []string `group:"baz"` 46 | } 47 | 48 | type out struct { 49 | dig.Out 50 | 51 | A A `name:"foo"` 52 | C C `name:"bar"` 53 | } 54 | 55 | type stringOut struct { 56 | dig.Out 57 | 58 | S string `group:"baz"` 59 | } 60 | 61 | c := digtest.New(t, dig.SetRand(rand.New(rand.NewSource(0)))) 62 | 63 | c.RequireProvide(func(i in) D { 64 | assert.Equal(t, []string{"bar", "baz", "foo"}, i.Strings) 65 | return D{} 66 | }) 67 | 68 | c.RequireProvide(func() out { 69 | return out{ 70 | A: A{}, 71 | C: C{}, 72 | } 73 | }) 74 | 75 | c.RequireProvide(func() A { return A{} }) 76 | c.RequireProvide(func() B { return B{} }) 77 | c.RequireProvide(func() C { return C{} }) 78 | 79 | c.RequireProvide(func(A) stringOut { return stringOut{S: "foo"} }) 80 | c.RequireProvide(func(B) stringOut { return stringOut{S: "bar"} }) 81 | c.RequireProvide(func(C) stringOut { return stringOut{S: "baz"} }) 82 | 83 | c.RequireInvoke(func(D) { 84 | }) 85 | 86 | s := c.String() 87 | 88 | // All nodes 89 | assert.Contains(t, s, `dig_test.A[name="foo"] -> deps: []`) 90 | assert.Contains(t, s, "dig_test.A -> deps: []") 91 | assert.Contains(t, s, "dig_test.B -> deps: []") 92 | assert.Contains(t, s, "dig_test.C -> deps: []") 93 | assert.Contains(t, s, `dig_test.C[name="bar"] -> deps: []`) 94 | assert.Contains(t, s, `dig_test.D -> deps: [dig_test.A[name="foo"] dig_test.B[optional] dig_test.C[optional, name="bar"] string[group="baz"]]`) 95 | assert.Contains(t, s, `string[group="baz"] -> deps: [dig_test.A]`) 96 | assert.Contains(t, s, `string[group="baz"] -> deps: [dig_test.B]`) 97 | assert.Contains(t, s, `string[group="baz"] -> deps: [dig_test.C]`) 98 | 99 | // Values 100 | assert.Contains(t, s, "dig_test.A => {}") 101 | assert.Contains(t, s, "dig_test.B => {}") 102 | assert.Contains(t, s, "dig_test.C => {}") 103 | assert.Contains(t, s, "dig_test.D => {}") 104 | assert.Contains(t, s, `dig_test.A[name="foo"] => {}`) 105 | assert.Contains(t, s, `dig_test.C[name="bar"] => {}`) 106 | assert.Contains(t, s, `string[group="baz"] => foo`) 107 | assert.Contains(t, s, `string[group="baz"] => bar`) 108 | assert.Contains(t, s, `string[group="baz"] => baz`) 109 | } 110 | -------------------------------------------------------------------------------- /testdata/dig_as_two.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=RL; 3 | graph [compound=true]; 4 | subgraph cluster_0 { 5 | label = "go.uber.org/dig_test"; 6 | constructor_0 [shape=plaintext label="TestVisualize.func4.1"]; 7 | "io.Reader" [label=]; 8 | "io.Writer" [label=]; 9 | } 10 | } -------------------------------------------------------------------------------- /testdata/empty.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=RL; 3 | graph [compound=true]; 4 | } -------------------------------------------------------------------------------- /testdata/error.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=RL; 3 | graph [compound=true]; 4 | "[type=dig_test.t1 group=g1]" [shape=diamond label=Group: g1> color=red]; 5 | "[type=dig_test.t1 group=g1]" -> "dig_test.t1[group=g1]0"; 6 | "[type=dig_test.t2 group=g2]" [shape=diamond label=Group: g2> color=orange]; 7 | "[type=dig_test.t2 group=g2]" -> "dig_test.t2[group=g2]0"; 8 | "[type=dig_test.t2 group=g2]" -> "dig_test.t2[group=g2]2"; 9 | subgraph cluster_0 { 10 | label = "go.uber.org/dig_test"; 11 | constructor_0 [shape=plaintext label="TestVisualize.func7.1"]; 12 | color=orange; 13 | "dig_test.t3[name=n3]" [label=Name: n3>]; 14 | "dig_test.t2[group=g2]0" [label=Group: g2>]; 15 | } 16 | constructor_0 -> "[type=dig_test.t1 group=g1]" [ltail=cluster_0]; 17 | subgraph cluster_1 { 18 | label = "go.uber.org/dig_test"; 19 | constructor_1 [shape=plaintext label="TestVisualize.func7.2"]; 20 | color=orange; 21 | "dig_test.t4" [label=]; 22 | } 23 | constructor_1 -> "dig_test.t3[name=n3]" [ltail=cluster_1]; 24 | constructor_1 -> "[type=dig_test.t2 group=g2]" [ltail=cluster_1]; 25 | subgraph cluster_2 { 26 | label = "go.uber.org/dig_test"; 27 | constructor_2 [shape=plaintext label="TestVisualize.func7.4"]; 28 | color=red; 29 | "dig_test.t1[group=g1]0" [label=Group: g1>]; 30 | "dig_test.t2[group=g2]2" [label=Group: g2>]; 31 | } 32 | "dig_test.t2[group=g2]0" [color=orange]; 33 | "dig_test.t4" [color=orange]; 34 | "dig_test.t1[group=g1]0" [color=red]; 35 | } -------------------------------------------------------------------------------- /testdata/grouped.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=RL; 3 | graph [compound=true]; 4 | "[type=dig_test.t3 group=foo]" [shape=diamond label=Group: foo>]; 5 | "[type=dig_test.t3 group=foo]" -> "dig_test.t3[group=foo]0"; 6 | "[type=dig_test.t3 group=foo]" -> "dig_test.t3[group=foo]1"; 7 | subgraph cluster_0 { 8 | label = "go.uber.org/dig_test"; 9 | constructor_0 [shape=plaintext label="TestVisualize.func6.1"]; 10 | "dig_test.t3[group=foo]0" [label=Group: foo>]; 11 | } 12 | subgraph cluster_1 { 13 | label = "go.uber.org/dig_test"; 14 | constructor_1 [shape=plaintext label="TestVisualize.func6.2"]; 15 | "dig_test.t3[group=foo]1" [label=Group: foo>]; 16 | } 17 | subgraph cluster_2 { 18 | label = "go.uber.org/dig_test"; 19 | constructor_2 [shape=plaintext label="TestVisualize.func6.3"]; 20 | "dig_test.t2" [label=]; 21 | } 22 | constructor_2 -> "[type=dig_test.t3 group=foo]" [ltail=cluster_2]; 23 | } -------------------------------------------------------------------------------- /testdata/missing.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=RL; 3 | graph [compound=true]; 4 | subgraph cluster_0 { 5 | label = "go.uber.org/dig_test"; 6 | constructor_0 [shape=plaintext label="TestVisualize.func8.1"]; 7 | color=orange; 8 | "dig_test.t4" [label=]; 9 | } 10 | constructor_0 -> "dig_test.t1" [ltail=cluster_0]; 11 | constructor_0 -> "dig_test.t2" [ltail=cluster_0]; 12 | constructor_0 -> "dig_test.t3" [ltail=cluster_0]; 13 | "dig_test.t4" [color=orange]; 14 | "dig_test.t1" [color=red]; 15 | "dig_test.t2" [color=red]; 16 | "dig_test.t3" [color=red]; 17 | } -------------------------------------------------------------------------------- /testdata/missingDep.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=RL; 3 | graph [compound=true]; 4 | "dig_test.t1" [color=red]; 5 | } -------------------------------------------------------------------------------- /testdata/named.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=RL; 3 | graph [compound=true]; 4 | subgraph cluster_0 { 5 | label = "go.uber.org/dig_test"; 6 | constructor_0 [shape=plaintext label="TestVisualize.func3.1"]; 7 | "dig_test.t1[name=bar]" [label=Name: bar>]; 8 | "dig_test.t2[name=baz]" [label=Name: baz>]; 9 | } 10 | constructor_0 -> "dig_test.t3[name=foo]" [ltail=cluster_0]; 11 | subgraph cluster_1 { 12 | label = "go.uber.org/dig_test"; 13 | constructor_1 [shape=plaintext label="TestVisualize.func3.2"]; 14 | "dig_test.t3[name=foo]" [label=Name: foo>]; 15 | } 16 | } -------------------------------------------------------------------------------- /testdata/optional.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=RL; 3 | graph [compound=true]; 4 | subgraph cluster_0 { 5 | label = "go.uber.org/dig_test"; 6 | constructor_0 [shape=plaintext label="TestVisualize.func5.1"]; 7 | "dig_test.t1" [label=]; 8 | } 9 | subgraph cluster_1 { 10 | label = "go.uber.org/dig_test"; 11 | constructor_1 [shape=plaintext label="TestVisualize.func5.2"]; 12 | "dig_test.t2" [label=]; 13 | } 14 | constructor_1 -> "dig_test.t1" [ltail=cluster_1 style=dashed]; 15 | } -------------------------------------------------------------------------------- /testdata/prune_constructor_result.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=RL; 3 | graph [compound=true]; 4 | "[type=dig_test.t2 group=g2]" [shape=diamond label=Group: g2> color=red]; 5 | "[type=dig_test.t2 group=g2]" -> "dig_test.t2[group=g2]1"; 6 | subgraph cluster_0 { 7 | label = "go.uber.org/dig_test"; 8 | constructor_0 [shape=plaintext label="TestVisualize.func7.6.1.2"]; 9 | color=orange; 10 | "dig_test.t4" [label=]; 11 | } 12 | constructor_0 -> "[type=dig_test.t2 group=g2]" [ltail=cluster_0]; 13 | subgraph cluster_1 { 14 | label = "go.uber.org/dig_test"; 15 | constructor_1 [shape=plaintext label="TestVisualize.func7.6.1.3"]; 16 | color=red; 17 | "dig_test.t2[group=g2]1" [label=Group: g2>]; 18 | } 19 | "dig_test.t4" [color=orange]; 20 | "dig_test.t2[group=g2]1" [color=red]; 21 | } -------------------------------------------------------------------------------- /testdata/prune_non_root_nodes.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=RL; 3 | graph [compound=true]; 4 | subgraph cluster_0 { 5 | label = "go.uber.org/dig_test"; 6 | constructor_0 [shape=plaintext label="TestVisualize.func7.6.2.2"]; 7 | color=red; 8 | "dig_test.t4" [label=]; 9 | } 10 | "dig_test.t4" [color=red]; 11 | } -------------------------------------------------------------------------------- /testdata/simple.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=RL; 3 | graph [compound=true]; 4 | subgraph cluster_0 { 5 | label = "go.uber.org/dig_test"; 6 | constructor_0 [shape=plaintext label="TestVisualize.func2.1"]; 7 | "dig_test.t1" [label=]; 8 | "dig_test.t2" [label=]; 9 | } 10 | subgraph cluster_1 { 11 | label = "go.uber.org/dig_test"; 12 | constructor_1 [shape=plaintext label="TestVisualize.func2.2"]; 13 | "dig_test.t3" [label=]; 14 | "dig_test.t4" [label=]; 15 | } 16 | constructor_1 -> "dig_test.t1" [ltail=cluster_1]; 17 | constructor_1 -> "dig_test.t2" [ltail=cluster_1]; 18 | } -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | // Version of the library. 24 | const Version = "1.20.0-dev" 25 | -------------------------------------------------------------------------------- /visualize.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "errors" 25 | "fmt" 26 | "io" 27 | "strconv" 28 | 29 | "go.uber.org/dig/internal/dot" 30 | ) 31 | 32 | // A VisualizeOption modifies the default behavior of Visualize. 33 | type VisualizeOption interface { 34 | applyVisualizeOption(*visualizeOptions) 35 | } 36 | 37 | type visualizeOptions struct { 38 | VisualizeError error 39 | } 40 | 41 | // VisualizeError includes a visualization of the given error in the output of 42 | // Visualize if an error was returned by Invoke or Provide. 43 | // 44 | // if err := c.Provide(...); err != nil { 45 | // dig.Visualize(c, w, dig.VisualizeError(err)) 46 | // } 47 | // 48 | // This option has no effect if the error was nil or if it didn't contain any 49 | // information to visualize. 50 | func VisualizeError(err error) VisualizeOption { 51 | return visualizeErrorOption{err} 52 | } 53 | 54 | type visualizeErrorOption struct{ err error } 55 | 56 | func (o visualizeErrorOption) String() string { 57 | return fmt.Sprintf("VisualizeError(%v)", o.err) 58 | } 59 | 60 | func (o visualizeErrorOption) applyVisualizeOption(opt *visualizeOptions) { 61 | opt.VisualizeError = o.err 62 | } 63 | 64 | func updateGraph(dg *dot.Graph, err error) error { 65 | var errs []errVisualizer 66 | // Unwrap error to find the root cause. 67 | for { 68 | if ev, ok := err.(errVisualizer); ok { 69 | errs = append(errs, ev) 70 | } 71 | e := errors.Unwrap(err) 72 | if e == nil { 73 | break 74 | } 75 | err = e 76 | } 77 | 78 | // If there are no errVisualizers included, we do not modify the graph. 79 | if len(errs) == 0 { 80 | return nil 81 | } 82 | 83 | // We iterate in reverse because the last element is the root cause. 84 | for i := len(errs) - 1; i >= 0; i-- { 85 | errs[i].updateGraph(dg) 86 | } 87 | 88 | // Remove non-error entries from the graph for readability. 89 | dg.PruneSuccess() 90 | 91 | return nil 92 | } 93 | 94 | // Visualize parses the graph in Container c into DOT format and writes it to 95 | // io.Writer w. 96 | func Visualize(c *Container, w io.Writer, opts ...VisualizeOption) error { 97 | dg := c.createGraph() 98 | 99 | var options visualizeOptions 100 | for _, o := range opts { 101 | o.applyVisualizeOption(&options) 102 | } 103 | 104 | if options.VisualizeError != nil { 105 | if err := updateGraph(dg, options.VisualizeError); err != nil { 106 | return err 107 | } 108 | } 109 | 110 | visualizeGraph(w, dg) 111 | return nil 112 | } 113 | 114 | func visualizeGraph(w io.Writer, dg *dot.Graph) { 115 | w.Write([]byte("digraph {\n\trankdir=RL;\n\tgraph [compound=true];\n")) 116 | for _, g := range dg.Groups { 117 | visualizeGroup(w, g) 118 | } 119 | for idx, c := range dg.Ctors { 120 | visualizeCtor(w, idx, c) 121 | } 122 | for _, f := range dg.Failed.TransitiveFailures { 123 | fmt.Fprintf(w, "\t%s [color=orange];\n", strconv.Quote(f.String())) 124 | } 125 | for _, f := range dg.Failed.RootCauses { 126 | fmt.Fprintf(w, "\t%s [color=red];\n", strconv.Quote(f.String())) 127 | } 128 | w.Write([]byte("}")) 129 | } 130 | 131 | func visualizeGroup(w io.Writer, g *dot.Group) { 132 | fmt.Fprintf(w, "\t%s [%s];\n", strconv.Quote(g.String()), g.Attributes()) 133 | for _, r := range g.Results { 134 | fmt.Fprintf(w, "\t%s -> %s;\n", strconv.Quote(g.String()), strconv.Quote(r.String())) 135 | } 136 | } 137 | 138 | func visualizeCtor(w io.Writer, index int, c *dot.Ctor) { 139 | fmt.Fprintf(w, "\tsubgraph cluster_%d {\n", index) 140 | if c.Package != "" { 141 | fmt.Fprintf(w, "\t\tlabel = %s;\n", strconv.Quote(c.Package)) 142 | } 143 | fmt.Fprintf(w, "\t\tconstructor_%d [shape=plaintext label=%s];\n", index, strconv.Quote(c.Name)) 144 | 145 | if c.ErrorType != 0 { 146 | fmt.Fprintf(w, "\t\tcolor=%s;\n", c.ErrorType.Color()) 147 | } 148 | for _, r := range c.Results { 149 | fmt.Fprintf(w, "\t\t%s [%s];\n", strconv.Quote(r.String()), r.Attributes()) 150 | } 151 | fmt.Fprintf(w, "\t}\n") 152 | for _, p := range c.Params { 153 | var optionalStyle string 154 | if p.Optional { 155 | optionalStyle = " style=dashed" 156 | } 157 | 158 | fmt.Fprintf(w, "\tconstructor_%d -> %s [ltail=cluster_%d%s];\n", index, strconv.Quote(p.String()), index, optionalStyle) 159 | } 160 | for _, p := range c.GroupParams { 161 | fmt.Fprintf(w, "\tconstructor_%d -> %s [ltail=cluster_%d];\n", index, strconv.Quote(p.String()), index) 162 | } 163 | } 164 | 165 | // CanVisualizeError returns true if the error is an errVisualizer. 166 | func CanVisualizeError(err error) bool { 167 | for { 168 | if _, ok := err.(errVisualizer); ok { 169 | return true 170 | } 171 | e := errors.Unwrap(err) 172 | if e == nil { 173 | break 174 | } 175 | err = e 176 | } 177 | 178 | return false 179 | } 180 | 181 | func (c *Container) createGraph() *dot.Graph { 182 | return c.scope.createGraph() 183 | } 184 | 185 | func (s *Scope) createGraph() *dot.Graph { 186 | dg := dot.NewGraph() 187 | 188 | s.addNodes(dg) 189 | 190 | return dg 191 | } 192 | 193 | func (s *Scope) addNodes(dg *dot.Graph) { 194 | for _, n := range s.nodes { 195 | dg.AddCtor(newDotCtor(n), n.paramList.DotParam(), n.resultList.DotResult()) 196 | } 197 | 198 | for _, cs := range s.childScopes { 199 | cs.addNodes(dg) 200 | } 201 | } 202 | 203 | func newDotCtor(n *constructorNode) *dot.Ctor { 204 | return &dot.Ctor{ 205 | ID: n.id, 206 | Name: n.location.Name, 207 | Package: n.location.Package, 208 | File: n.location.File, 209 | Line: n.location.Line, 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /visualize_golden_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "bytes" 25 | "flag" 26 | "os" 27 | "path/filepath" 28 | "testing" 29 | 30 | "github.com/stretchr/testify/assert" 31 | "github.com/stretchr/testify/require" 32 | ) 33 | 34 | var generate = flag.Bool("generate", false, "generates output to testdata/ if set") 35 | 36 | func VerifyVisualization(t *testing.T, testname string, c *Container, opts ...VisualizeOption) { 37 | var b bytes.Buffer 38 | require.NoError(t, Visualize(c, &b, opts...)) 39 | got := b.Bytes() 40 | got = bytes.ReplaceAll(got, []byte("\r\n"), []byte("\n")) // normalize line endings 41 | 42 | dotFile := filepath.Join("testdata", testname+".dot") 43 | 44 | if *generate { 45 | err := os.WriteFile(dotFile, got, 0o644) 46 | require.NoError(t, err) 47 | return 48 | } 49 | 50 | want, err := os.ReadFile(dotFile) 51 | require.NoError(t, err) 52 | want = bytes.ReplaceAll(want, []byte("\r\n"), []byte("\n")) // normalize line endings 53 | 54 | assert.Equal(t, string(want), string(got), 55 | "Output did not match. Make sure you updated the testdata by running 'go test -generate'") 56 | } 57 | -------------------------------------------------------------------------------- /visualize_int_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package dig 22 | 23 | import ( 24 | "errors" 25 | "fmt" 26 | "io" 27 | "testing" 28 | 29 | "github.com/stretchr/testify/assert" 30 | "go.uber.org/dig/internal/dot" 31 | ) 32 | 33 | func (c *Container) CreateGraph() *dot.Graph { 34 | return c.createGraph() 35 | } 36 | 37 | func (s *Scope) CreateGraph() *dot.Graph { 38 | return s.createGraph() 39 | } 40 | 41 | type nestedErr struct { 42 | err error 43 | } 44 | 45 | var _ digError = nestedErr{} 46 | 47 | func (e nestedErr) Error() string { 48 | return fmt.Sprint(e) 49 | } 50 | 51 | func (e nestedErr) Unwrap() error { 52 | return e.err 53 | } 54 | 55 | func (e nestedErr) writeMessage(w io.Writer, _ string) { 56 | io.WriteString(w, "oh no") 57 | } 58 | 59 | func (e nestedErr) Format(w fmt.State, c rune) { 60 | formatError(e, w, c) 61 | } 62 | 63 | type visualizableErr struct{} 64 | 65 | func (err visualizableErr) Error() string { return "great sadness" } 66 | func (err visualizableErr) updateGraph(dg *dot.Graph) {} 67 | 68 | func TestCanVisualizeError(t *testing.T) { 69 | tests := []struct { 70 | desc string 71 | err error 72 | canVisualize bool 73 | }{ 74 | { 75 | desc: "unvisualizable error", 76 | err: errors.New("great sadness"), 77 | canVisualize: false, 78 | }, 79 | { 80 | desc: "nested unvisualizable error", 81 | err: nestedErr{err: errors.New("great sadness")}, 82 | canVisualize: false, 83 | }, 84 | { 85 | desc: "visualizable error", 86 | err: visualizableErr{}, 87 | canVisualize: true, 88 | }, 89 | { 90 | desc: "nested visualizable error", 91 | err: nestedErr{err: visualizableErr{}}, 92 | canVisualize: true, 93 | }, 94 | } 95 | 96 | for _, tt := range tests { 97 | t.Run(tt.desc, func(t *testing.T) { 98 | assert.Equal(t, tt.canVisualize, CanVisualizeError(tt.err)) 99 | }) 100 | } 101 | } 102 | --------------------------------------------------------------------------------