package dig
import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"go.uber.org/dig/internal/digerror"
"go.uber.org/dig/internal/dot"
)
type param interface {
fmt .Stringer
Build(store containerStore ) (reflect .Value , error )
DotParam() []*dot .Param
}
var (
_ param = paramSingle {}
_ param = paramObject {}
_ param = paramList {}
_ param = paramGroupedSlice {}
)
func newParam(t reflect .Type , c containerStore ) (param , error ) {
switch {
case IsOut (t ) || (t .Kind () == reflect .Ptr && IsOut (t .Elem ())) || embedsType (t , _outPtrType ):
return nil , newErrInvalidInput (fmt .Sprintf (
"cannot depend on result objects: %v embeds a dig.Out" , t ), nil )
case IsIn (t ):
return newParamObject (t , c )
case embedsType (t , _inPtrType ):
return nil , newErrInvalidInput (fmt .Sprintf (
"cannot build a parameter object by embedding *dig.In, embed dig.In instead: %v embeds *dig.In" , t ), nil )
case t .Kind () == reflect .Ptr && IsIn (t .Elem ()):
return nil , newErrInvalidInput (fmt .Sprintf (
"cannot depend on a pointer to a parameter object, use a value instead: %v is a pointer to a struct that embeds dig.In" , t ), nil )
default :
return paramSingle {Type : t }, nil
}
}
type paramList struct {
ctype reflect .Type
Params []param
}
func (pl paramList ) DotParam () []*dot .Param {
var types []*dot .Param
for _ , param := range pl .Params {
types = append (types , param .DotParam ()...)
}
return types
}
func (pl paramList ) String () string {
args := make ([]string , len (pl .Params ))
for i , p := range pl .Params {
args [i ] = p .String ()
}
return fmt .Sprint (args )
}
func newParamList(ctype reflect .Type , c containerStore ) (paramList , error ) {
numArgs := ctype .NumIn ()
if ctype .IsVariadic () {
numArgs --
}
pl := paramList {
ctype : ctype ,
Params : make ([]param , numArgs ),
}
for i := 0 ; i < numArgs ; i ++ {
p , err := newParam (ctype .In (i ), c )
if err != nil {
return pl , newErrInvalidInput (fmt .Sprintf ("bad argument %d" , i +1 ), err )
}
pl .Params [i ] = p
}
return pl , nil
}
func (pl paramList ) Build (containerStore ) (reflect .Value , error ) {
digerror .BugPanicf ("paramList.Build() must never be called" )
panic ("" )
}
func (pl paramList ) BuildList (c containerStore ) ([]reflect .Value , error ) {
args := make ([]reflect .Value , len (pl .Params ))
for i , p := range pl .Params {
var err error
args [i ], err = p .Build (c )
if err != nil {
return nil , err
}
}
return args , nil
}
type paramSingle struct {
Name string
Optional bool
Type reflect .Type
}
func (ps paramSingle ) DotParam () []*dot .Param {
return []*dot .Param {
{
Node : &dot .Node {
Type : ps .Type ,
Name : ps .Name ,
},
Optional : ps .Optional ,
},
}
}
func (ps paramSingle ) String () string {
var opts []string
if ps .Optional {
opts = append (opts , "optional" )
}
if ps .Name != "" {
opts = append (opts , fmt .Sprintf ("name=%q" , ps .Name ))
}
if len (opts ) == 0 {
return fmt .Sprint (ps .Type )
}
return fmt .Sprintf ("%v[%v]" , ps .Type , strings .Join (opts , ", " ))
}
func (ps paramSingle ) getDecoratedValue (c containerStore ) (reflect .Value , bool ) {
for _ , c := range c .storesToRoot () {
if v , ok := c .getDecoratedValue (ps .Name , ps .Type ); ok {
return v , ok
}
}
return _noValue , false
}
func (ps paramSingle ) buildWithDecorators (c containerStore ) (v reflect .Value , found bool , err error ) {
var (
d decorator
decoratingScope containerStore
)
stores := c .storesToRoot ()
for _ , s := range stores {
if d , found = s .getValueDecorator (ps .Name , ps .Type ); !found {
continue
}
if d .State () == decoratorOnStack {
d = nil
continue
}
decoratingScope = s
break
}
if !found || d == nil {
return _noValue , false , nil
}
if err = d .Call (decoratingScope ); err != nil {
v , err = _noValue , errParamSingleFailed {
CtorID : 1 ,
Key : key {t : ps .Type , name : ps .Name },
Reason : err ,
}
return v , found , err
}
v , _ = decoratingScope .getDecoratedValue (ps .Name , ps .Type )
return
}
func (ps paramSingle ) Build (c containerStore ) (reflect .Value , error ) {
v , found , err := ps .buildWithDecorators (c )
if found {
return v , err
}
if v , ok := ps .getDecoratedValue (c ); ok {
return v , nil
}
var providers []provider
var providingContainer containerStore
for _ , container := range c .storesToRoot () {
if v , ok := container .getValue (ps .Name , ps .Type ); ok {
return v , nil
}
providers = container .getValueProviders (ps .Name , ps .Type )
if len (providers ) > 0 {
providingContainer = container
break
}
}
if len (providers ) == 0 {
if ps .Optional {
return reflect .Zero (ps .Type ), nil
}
return _noValue , newErrMissingTypes (c , key {name : ps .Name , t : ps .Type })
}
for _ , n := range providers {
err := n .Call (n .OrigScope ())
if err == nil {
continue
}
if errors .As (err , new (errMissingDependencies )) && ps .Optional {
return reflect .Zero (ps .Type ), nil
}
return _noValue , errParamSingleFailed {
CtorID : n .ID (),
Key : key {t : ps .Type , name : ps .Name },
Reason : err ,
}
}
v , _ = providingContainer .getValue (ps .Name , ps .Type )
return v , nil
}
type paramObject struct {
Type reflect .Type
Fields []paramObjectField
FieldOrders []int
}
func (po paramObject ) DotParam () []*dot .Param {
var types []*dot .Param
for _ , field := range po .Fields {
types = append (types , field .DotParam ()...)
}
return types
}
func (po paramObject ) String () string {
fields := make ([]string , len (po .Fields ))
for i , f := range po .Fields {
fields [i ] = f .Param .String ()
}
return strings .Join (fields , " " )
}
func getParamOrder(gh *graphHolder , param param ) []int {
var orders []int
switch p := param .(type ) {
case paramSingle :
providers := gh .s .getAllValueProviders (p .Name , p .Type )
for _ , provider := range providers {
orders = append (orders , provider .Order (gh .s ))
}
case paramGroupedSlice :
orders = append (orders , p .orders [gh .s ])
case paramObject :
for _ , pf := range p .Fields {
orders = append (orders , getParamOrder (gh , pf .Param )...)
}
}
return orders
}
func newParamObject(t reflect .Type , c containerStore ) (paramObject , error ) {
po := paramObject {Type : t }
var ignoreUnexported bool
for i := 0 ; i < t .NumField (); i ++ {
f := t .Field (i )
if f .Type == _inType {
var err error
ignoreUnexported , err = isIgnoreUnexportedSet (f )
if err != nil {
return po , err
}
break
}
}
for i := 0 ; i < t .NumField (); i ++ {
f := t .Field (i )
if f .Type == _inType {
continue
}
if f .PkgPath != "" && ignoreUnexported {
continue
}
pof , err := newParamObjectField (i , f , c )
if err != nil {
return po , newErrInvalidInput (
fmt .Sprintf ("bad field %q of %v" , f .Name , t ), err )
}
po .Fields = append (po .Fields , pof )
}
return po , nil
}
func (po paramObject ) Build (c containerStore ) (reflect .Value , error ) {
dest := reflect .New (po .Type ).Elem ()
var softGroupsQueue []paramObjectField
var fields []paramObjectField
for _ , f := range po .Fields {
if p , ok := f .Param .(paramGroupedSlice ); ok && p .Soft {
softGroupsQueue = append (softGroupsQueue , f )
continue
}
fields = append (fields , f )
}
fields = append (fields , softGroupsQueue ...)
for _ , f := range fields {
v , err := f .Build (c )
if err != nil {
return dest , err
}
dest .Field (f .FieldIndex ).Set (v )
}
return dest , nil
}
type paramObjectField struct {
FieldName string
FieldIndex int
Param param
}
func (pof paramObjectField ) DotParam () []*dot .Param {
return pof .Param .DotParam ()
}
func newParamObjectField(idx int , f reflect .StructField , c containerStore ) (paramObjectField , error ) {
pof := paramObjectField {
FieldName : f .Name ,
FieldIndex : idx ,
}
var p param
switch {
case f .PkgPath != "" :
return pof , newErrInvalidInput (
fmt .Sprintf ("unexported fields not allowed in dig.In, did you mean to export %q (%v)?" , f .Name , f .Type ), nil )
case f .Tag .Get (_groupTag ) != "" :
var err error
p , err = newParamGroupedSlice (f , c )
if err != nil {
return pof , err
}
default :
var err error
p , err = newParam (f .Type , c )
if err != nil {
return pof , err
}
}
if ps , ok := p .(paramSingle ); ok {
ps .Name = f .Tag .Get (_nameTag )
var err error
ps .Optional , err = isFieldOptional (f )
if err != nil {
return pof , err
}
p = ps
}
pof .Param = p
return pof , nil
}
func (pof paramObjectField ) Build (c containerStore ) (reflect .Value , error ) {
v , err := pof .Param .Build (c )
if err != nil {
return v , err
}
return v , nil
}
type paramGroupedSlice struct {
Group string
Type reflect .Type
Soft bool
orders map [*Scope ]int
}
func (pt paramGroupedSlice ) String () string {
return fmt .Sprintf ("%v[group=%q]" , pt .Type .Elem (), pt .Group )
}
func (pt paramGroupedSlice ) DotParam () []*dot .Param {
return []*dot .Param {
{
Node : &dot .Node {
Type : pt .Type ,
Group : pt .Group ,
},
},
}
}
func newParamGroupedSlice(f reflect .StructField , c containerStore ) (paramGroupedSlice , error ) {
g , err := parseGroupString (f .Tag .Get (_groupTag ))
if err != nil {
return paramGroupedSlice {}, err
}
pg := paramGroupedSlice {
Group : g .Name ,
Type : f .Type ,
orders : make (map [*Scope ]int ),
Soft : g .Soft ,
}
name := f .Tag .Get (_nameTag )
optional , _ := isFieldOptional (f )
switch {
case f .Type .Kind () != reflect .Slice :
return pg , newErrInvalidInput (
fmt .Sprintf ("value groups may be consumed as slices only: field %q (%v) is not a slice" , f .Name , f .Type ), nil )
case g .Flatten :
return pg , newErrInvalidInput (
fmt .Sprintf ("cannot use flatten in parameter value groups: field %q (%v) specifies flatten" , f .Name , f .Type ), nil )
case name != "" :
return pg , newErrInvalidInput (
fmt .Sprintf ("cannot use named values with value groups: name:%q requested with group:%q" , name , pg .Group ), nil )
case optional :
return pg , newErrInvalidInput ("value groups cannot be optional" , nil )
}
c .newGraphNode (&pg , pg .orders )
return pg , nil
}
func (pt paramGroupedSlice ) getDecoratedValues (c containerStore ) (reflect .Value , bool ) {
for _ , c := range c .storesToRoot () {
if items , ok := c .getDecoratedValueGroup (pt .Group , pt .Type ); ok {
return items , true
}
}
return _noValue , false
}
func (pt paramGroupedSlice ) callGroupDecorators (c containerStore ) error {
stores := c .storesToRoot ()
for i := len (stores ) - 1 ; i >= 0 ; i -- {
c := stores [i ]
if d , found := c .getGroupDecorator (pt .Group , pt .Type .Elem ()); found {
if d .State () == decoratorOnStack {
continue
}
if err := d .Call (c ); err != nil {
return errParamGroupFailed {
CtorID : d .ID (),
Key : key {group : pt .Group , t : pt .Type .Elem ()},
Reason : err ,
}
}
}
}
return nil
}
func (pt paramGroupedSlice ) callGroupProviders (c containerStore ) (int , error ) {
itemCount := 0
for _ , c := range c .storesToRoot () {
providers := c .getGroupProviders (pt .Group , pt .Type .Elem ())
itemCount += len (providers )
for _ , n := range providers {
if err := n .Call (n .OrigScope ()); err != nil {
return 0 , errParamGroupFailed {
CtorID : n .ID (),
Key : key {group : pt .Group , t : pt .Type .Elem ()},
Reason : err ,
}
}
}
}
return itemCount , nil
}
func (pt paramGroupedSlice ) Build (c containerStore ) (reflect .Value , error ) {
if err := pt .callGroupDecorators (c ); err != nil {
return _noValue , err
}
if decoratedItems , ok := pt .getDecoratedValues (c ); ok {
return decoratedItems , nil
}
itemCount := 0
if !pt .Soft {
var err error
itemCount , err = pt .callGroupProviders (c )
if err != nil {
return _noValue , err
}
}
stores := c .storesToRoot ()
result := reflect .MakeSlice (pt .Type , 0 , itemCount )
for _ , c := range stores {
result = reflect .Append (result , c .getValueGroup (pt .Group , pt .Type .Elem ())...)
}
return result , nil
}
func isIgnoreUnexportedSet(f reflect .StructField ) (bool , error ) {
tag := f .Tag .Get (_ignoreUnexportedTag )
if tag == "" {
return false , nil
}
allowed , err := strconv .ParseBool (tag )
if err != nil {
err = newErrInvalidInput (
fmt .Sprintf ("invalid value %q for %q tag on field %v" , tag , _ignoreUnexportedTag , f .Name ), err )
}
return allowed , err
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .