|
| 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 decorator interface { |
| 32 | + Call(c containerStore) error |
| 33 | + ID() dot.CtorID |
| 34 | +} |
| 35 | + |
| 36 | +type decoratorNode struct { |
| 37 | + dcor interface{} |
| 38 | + dtype reflect.Type |
| 39 | + |
| 40 | + id dot.CtorID |
| 41 | + |
| 42 | + // Location where this function was defined. |
| 43 | + location *digreflect.Func |
| 44 | + |
| 45 | + // Whether the decorator owned by this node was already called. |
| 46 | + called bool |
| 47 | + |
| 48 | + // Parameters of the decorator. |
| 49 | + params paramList |
| 50 | + |
| 51 | + // Results of the decorator. |
| 52 | + results resultList |
| 53 | + |
| 54 | + // order of this node in each Scopes' graphHolders. |
| 55 | + orders map[*Scope]int |
| 56 | + |
| 57 | + // scope this node was originally provided to. |
| 58 | + s *Scope |
| 59 | +} |
| 60 | + |
| 61 | +func newDecoratorNode(dcor interface{}, s *Scope) (*decoratorNode, error) { |
| 62 | + dval := reflect.ValueOf(dcor) |
| 63 | + dtype := dval.Type() |
| 64 | + dptr := dval.Pointer() |
| 65 | + |
| 66 | + pl, err := newParamList(dtype, s) |
| 67 | + if err != nil { |
| 68 | + return nil, err |
| 69 | + } |
| 70 | + |
| 71 | + rl, err := newResultList(dtype, resultOptions{}) |
| 72 | + if err != nil { |
| 73 | + return nil, err |
| 74 | + } |
| 75 | + |
| 76 | + n := &decoratorNode{ |
| 77 | + dcor: dcor, |
| 78 | + dtype: dtype, |
| 79 | + id: dot.CtorID(dptr), |
| 80 | + location: digreflect.InspectFunc(dcor), |
| 81 | + orders: make(map[*Scope]int), |
| 82 | + params: pl, |
| 83 | + results: rl, |
| 84 | + s: s, |
| 85 | + } |
| 86 | + return n, nil |
| 87 | +} |
| 88 | + |
| 89 | +func (n *decoratorNode) Call(s containerStore) error { |
| 90 | + if n.called { |
| 91 | + return nil |
| 92 | + } |
| 93 | + |
| 94 | + if err := shallowCheckDependencies(s, n.params); err != nil { |
| 95 | + return errMissingDependencies{ |
| 96 | + Func: n.location, |
| 97 | + Reason: err, |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + args, err := n.params.BuildList(n.s, true /* decorating */) |
| 102 | + if err != nil { |
| 103 | + return errArgumentsFailed{ |
| 104 | + Func: n.location, |
| 105 | + Reason: err, |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + results := reflect.ValueOf(n.dcor).Call(args) |
| 110 | + if err := n.results.ExtractList(n.s, true /* decorated */, results); err != nil { |
| 111 | + return err |
| 112 | + } |
| 113 | + n.called = true |
| 114 | + return nil |
| 115 | +} |
| 116 | + |
| 117 | +func (n *decoratorNode) ID() dot.CtorID { return n.id } |
| 118 | + |
| 119 | +// DecorateOption modifies the default behavior of Provide. |
| 120 | +// Currently, there is no implementation of it yet. |
| 121 | +type DecorateOption interface { |
| 122 | + noOptionsYet() |
| 123 | +} |
| 124 | + |
| 125 | +// Decorate provides a decorator for a type that has already been provided in the Container. |
| 126 | +// Decorations at this level affect all scopes of the container. |
| 127 | +// See Scope.Decorate for information on how to use this method. |
| 128 | +func (c *Container) Decorate(decorator interface{}, opts ...DecorateOption) error { |
| 129 | + return c.scope.Decorate(decorator, opts...) |
| 130 | +} |
| 131 | + |
| 132 | +// Decorate provides a decorator for a type that has already been provided in the Scope. |
| 133 | +// |
| 134 | +// Similar to Provide, Decorate takes in a function with zero or more dependencies and one |
| 135 | +// or more results. Decorate can be used to modify a type that was already introduced to the |
| 136 | +// Scope, or completely replace it with a new object. |
| 137 | +// |
| 138 | +// For example, |
| 139 | +// s.Decorate(func(log *zap.Logger) *zap.Logger { |
| 140 | +// return log.Named("myapp") |
| 141 | +// }) |
| 142 | +// |
| 143 | +// This takes in a value, augments it with a name, and returns a replacement for it. Functions |
| 144 | +// in the Scope's dependency graph that use *zap.Logger will now use the *zap.Logger |
| 145 | +// returned by this decorator. |
| 146 | +// |
| 147 | +// A decorator can also take in multiple parameters and replace one of them: |
| 148 | +// s.Decorate(func(log *zap.Logger, cfg *Config) *zap.Logger { |
| 149 | +// return log.Named(cfg.Name) |
| 150 | +// }) |
| 151 | +// |
| 152 | +// Or replace a subset of them: |
| 153 | +// s.Decorate(func( |
| 154 | +// log *zap.Logger, |
| 155 | +// cfg *Config, |
| 156 | +// scope metrics.Scope |
| 157 | +// ) (*zap.Logger, metrics.Scope) { |
| 158 | +// log = log.Named(cfg.Name) |
| 159 | +// scope = scope.With(metrics.Tag("service", cfg.Name)) |
| 160 | +// return log, scope |
| 161 | +// }) |
| 162 | +// |
| 163 | +// Decorating a Scope affects all the child scopes of this Scope. |
| 164 | +// |
| 165 | +// Similar to a provider, the decorator function gets called *at most once*. |
| 166 | +func (s *Scope) Decorate(decorator interface{}, opts ...DecorateOption) error { |
| 167 | + _ = opts // there are no options at this time |
| 168 | + |
| 169 | + dn, err := newDecoratorNode(decorator, s) |
| 170 | + if err != nil { |
| 171 | + return err |
| 172 | + } |
| 173 | + |
| 174 | + keys := findResultKeys(dn.results) |
| 175 | + for _, k := range keys { |
| 176 | + if len(s.decorators[k]) > 0 { |
| 177 | + return fmt.Errorf("cannot decorate using function %v: %s already decorated", |
| 178 | + dn.dtype, |
| 179 | + k, |
| 180 | + ) |
| 181 | + } |
| 182 | + s.decorators[k] = append(s.decorators[k], dn) |
| 183 | + } |
| 184 | + return nil |
| 185 | +} |
| 186 | + |
| 187 | +func findResultKeys(r resultList) []key { |
| 188 | + // use BFS to search for all keys included in a resultList. |
| 189 | + var ( |
| 190 | + q []result |
| 191 | + keys []key |
| 192 | + ) |
| 193 | + q = append(q, r) |
| 194 | + |
| 195 | + for len(q) > 0 { |
| 196 | + res := q[0] |
| 197 | + q = q[1:] |
| 198 | + |
| 199 | + switch innerResult := res.(type) { |
| 200 | + case resultSingle: |
| 201 | + keys = append(keys, key{t: innerResult.Type, name: innerResult.Name}) |
| 202 | + case resultGrouped: |
| 203 | + keys = append(keys, key{t: innerResult.Type.Elem(), group: innerResult.Group}) |
| 204 | + case resultObject: |
| 205 | + for _, f := range innerResult.Fields { |
| 206 | + q = append(q, f.Result) |
| 207 | + } |
| 208 | + case resultList: |
| 209 | + q = append(q, innerResult.Results...) |
| 210 | + } |
| 211 | + } |
| 212 | + return keys |
| 213 | +} |
0 commit comments