status-go/vendor/go.uber.org/dig/decorate.go

316 lines
8.0 KiB
Go

// Copyright (c) 2022 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package dig
import (
"fmt"
"reflect"
"go.uber.org/dig/internal/digreflect"
"go.uber.org/dig/internal/dot"
)
type decoratorState int
const (
decoratorReady decoratorState = iota
decoratorOnStack
decoratorCalled
)
type decorator interface {
Call(c containerStore) error
ID() dot.CtorID
State() decoratorState
}
type decoratorNode struct {
dcor interface{}
dtype reflect.Type
id dot.CtorID
// Location where this function was defined.
location *digreflect.Func
// Current state of this decorator
state decoratorState
// Parameters of the decorator.
params paramList
// Results of the decorator.
results resultList
// Order of this node in each Scopes' graphHolders.
orders map[*Scope]int
// Scope this node was originally provided to.
s *Scope
// Callback for this decorator, if there is one.
callback Callback
}
func newDecoratorNode(dcor interface{}, s *Scope, opts decorateOptions) (*decoratorNode, error) {
dval := reflect.ValueOf(dcor)
dtype := dval.Type()
dptr := dval.Pointer()
pl, err := newParamList(dtype, s)
if err != nil {
return nil, err
}
rl, err := newResultList(dtype, resultOptions{})
if err != nil {
return nil, err
}
n := &decoratorNode{
dcor: dcor,
dtype: dtype,
id: dot.CtorID(dptr),
location: digreflect.InspectFunc(dcor),
orders: make(map[*Scope]int),
params: pl,
results: rl,
s: s,
callback: opts.Callback,
}
return n, nil
}
func (n *decoratorNode) Call(s containerStore) (err error) {
if n.state == decoratorCalled {
return nil
}
n.state = decoratorOnStack
if err := shallowCheckDependencies(s, n.params); err != nil {
return errMissingDependencies{
Func: n.location,
Reason: err,
}
}
args, err := n.params.BuildList(n.s)
if err != nil {
return errArgumentsFailed{
Func: n.location,
Reason: err,
}
}
if n.callback != nil {
start := s.clock().Now()
// Wrap in separate func to include PanicErrors
defer func() {
n.callback(CallbackInfo{
Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name),
Error: err,
Runtime: s.clock().Since(start),
})
}()
}
if n.s.recoverFromPanics {
defer func() {
if p := recover(); p != nil {
err = PanicError{
fn: n.location,
Panic: p,
}
}
}()
}
results := s.invoker()(reflect.ValueOf(n.dcor), args)
if err = n.results.ExtractList(n.s, true /* decorated */, results); err != nil {
return err
}
n.state = decoratorCalled
return nil
}
func (n *decoratorNode) ID() dot.CtorID { return n.id }
func (n *decoratorNode) State() decoratorState { return n.state }
// DecorateOption modifies the default behavior of Decorate.
type DecorateOption interface {
apply(*decorateOptions)
}
type decorateOptions struct {
Info *DecorateInfo
Callback Callback
}
// FillDecorateInfo is a DecorateOption that writes info on what Dig was
// able to get out of the provided decorator into the provided DecorateInfo.
func FillDecorateInfo(info *DecorateInfo) DecorateOption {
return fillDecorateInfoOption{info: info}
}
type fillDecorateInfoOption struct{ info *DecorateInfo }
func (o fillDecorateInfoOption) String() string {
return fmt.Sprintf("FillDecorateInfo(%p)", o.info)
}
func (o fillDecorateInfoOption) apply(opts *decorateOptions) {
opts.Info = o.info
}
// DecorateInfo provides information about the decorator's inputs and outputs
// types as strings, as well as the ID of the decorator supplied to the Container.
type DecorateInfo struct {
ID ID
Inputs []*Input
Outputs []*Output
}
// Decorate provides a decorator for a type that has already been provided in the Container.
// Decorations at this level affect all scopes of the container.
// See Scope.Decorate for information on how to use this method.
func (c *Container) Decorate(decorator interface{}, opts ...DecorateOption) error {
return c.scope.Decorate(decorator, opts...)
}
// Decorate provides a decorator for a type that has already been provided in the Scope.
//
// Similar to Provide, Decorate takes in a function with zero or more dependencies and one
// or more results. Decorate can be used to modify a type that was already introduced to the
// Scope, or completely replace it with a new object.
//
// For example,
//
// s.Decorate(func(log *zap.Logger) *zap.Logger {
// return log.Named("myapp")
// })
//
// This takes in a value, augments it with a name, and returns a replacement for it. Functions
// in the Scope's dependency graph that use *zap.Logger will now use the *zap.Logger
// returned by this decorator.
//
// A decorator can also take in multiple parameters and replace one of them:
//
// s.Decorate(func(log *zap.Logger, cfg *Config) *zap.Logger {
// return log.Named(cfg.Name)
// })
//
// Or replace a subset of them:
//
// s.Decorate(func(
// log *zap.Logger,
// cfg *Config,
// scope metrics.Scope
// ) (*zap.Logger, metrics.Scope) {
// log = log.Named(cfg.Name)
// scope = scope.With(metrics.Tag("service", cfg.Name))
// return log, scope
// })
//
// Decorating a Scope affects all the child scopes of this Scope.
//
// Similar to a provider, the decorator function gets called *at most once*.
func (s *Scope) Decorate(decorator interface{}, opts ...DecorateOption) error {
var options decorateOptions
for _, opt := range opts {
opt.apply(&options)
}
dn, err := newDecoratorNode(decorator, s, options)
if err != nil {
return err
}
keys, err := findResultKeys(dn.results)
if err != nil {
return err
}
for _, k := range keys {
if _, ok := s.decorators[k]; ok {
return newErrInvalidInput(
fmt.Sprintf("cannot decorate using function %v: %s already decorated", dn.dtype, k), nil)
}
s.decorators[k] = dn
}
if info := options.Info; info != nil {
params := dn.params.DotParam()
results := dn.results.DotResult()
info.ID = (ID)(dn.id)
info.Inputs = make([]*Input, len(params))
info.Outputs = make([]*Output, len(results))
for i, param := range params {
info.Inputs[i] = &Input{
t: param.Type,
optional: param.Optional,
name: param.Name,
group: param.Group,
}
}
for i, res := range results {
info.Outputs[i] = &Output{
t: res.Type,
name: res.Name,
group: res.Group,
}
}
}
return nil
}
func findResultKeys(r resultList) ([]key, error) {
// use BFS to search for all keys included in a resultList.
var (
q []result
keys []key
)
q = append(q, r)
for len(q) > 0 {
res := q[0]
q = q[1:]
switch innerResult := res.(type) {
case resultSingle:
keys = append(keys, key{t: innerResult.Type, name: innerResult.Name})
case resultGrouped:
if innerResult.Type.Kind() != reflect.Slice {
return nil, newErrInvalidInput("decorating a value group requires decorating the entire value group, not a single value", nil)
}
keys = append(keys, key{t: innerResult.Type.Elem(), group: innerResult.Group})
case resultObject:
for _, f := range innerResult.Fields {
q = append(q, f.Result)
}
case resultList:
q = append(q, innerResult.Results...)
}
}
return keys, nil
}