2020-02-29 13:18:15 +01:00
package hero
import (
"fmt"
"reflect"
"github.com/kataras/iris/v12/context"
)
type (
2020-03-01 07:34:53 +01:00
// DependencyHandler is the native function declaration which implementors should return a value match to an input.
2022-03-01 20:26:02 +01:00
DependencyHandler = func ( ctx * context . Context , input * Input ) ( reflect . Value , error )
// DependencyMatchFunc type alias describes dependency
// match function with an input (field or parameter).
//
// See "DependencyMatcher" too, which can be used on a Container to
// change the way dependencies are matched to inputs for all dependencies.
DependencyMatchFunc = func ( in reflect . Type ) bool
2020-03-01 07:34:53 +01:00
// Dependency describes the design-time dependency to be injected at serve time.
// Contains its source location, the dependency handler (provider) itself and information
// such as static for static struct values or explicit to bind a value to its exact DestType and not if just assignable to it (interfaces).
2020-02-29 13:18:15 +01:00
Dependency struct {
OriginalValue interface { } // Used for debugging and for logging only.
Source Source
Handle DependencyHandler
// It's the exact type of return to bind, if declared to return <T>, otherwise nil.
DestType reflect . Type
Static bool
2021-02-21 21:24:01 +01:00
// If true then input and dependency DestType should be indedical,
2020-02-29 13:18:15 +01:00
// not just assiginable to each other.
// Example of use case: depenendency like time.Time that we want to be bindable
// only to time.Time inputs and not to a service with a `String() string` method that time.Time struct implements too.
Explicit bool
2022-03-01 20:26:02 +01:00
// Match holds the matcher. Defaults to the Container's one.
Match DependencyMatchFunc
2020-02-29 13:18:15 +01:00
}
)
// Explicitly sets Explicit option to true.
// See `Dependency.Explicit` field godoc for more.
//
// Returns itself.
func ( d * Dependency ) Explicitly ( ) * Dependency {
d . Explicit = true
return d
}
func ( d * Dependency ) String ( ) string {
sourceLine := d . Source . String ( )
val := d . OriginalValue
if val == nil {
val = d . Handle
}
return fmt . Sprintf ( "%s (%#+v)" , sourceLine , val )
}
// NewDependency converts a function or a function which accepts other dependencies or static struct value to a *Dependency.
//
// See `Container.Handler` for more.
2022-03-01 20:26:02 +01:00
func NewDependency ( dependency interface { } , funcDependencies ... * Dependency ) * Dependency { // used only on tests.
return newDependency ( dependency , false , nil , funcDependencies ... )
2021-04-22 13:00:00 +02:00
}
2022-03-01 20:26:02 +01:00
func newDependency ( dependency interface { } , disablePayloadAutoBinding bool , matchDependency DependencyMatcher , funcDependencies ... * Dependency ) * Dependency {
2020-02-29 13:18:15 +01:00
if dependency == nil {
panic ( fmt . Sprintf ( "bad value: nil: %T" , dependency ) )
}
if d , ok := dependency . ( * Dependency ) ; ok {
// already a *Dependency.
return d
}
v := valueOf ( dependency )
if ! goodVal ( v ) {
panic ( fmt . Sprintf ( "bad value: %#+v" , dependency ) )
}
2022-03-01 20:26:02 +01:00
if matchDependency == nil {
matchDependency = DefaultDependencyMatcher
}
2020-02-29 13:18:15 +01:00
dest := & Dependency {
Source : newSource ( v ) ,
OriginalValue : dependency ,
}
2022-03-01 20:26:02 +01:00
dest . Match = ToDependencyMatchFunc ( dest , matchDependency )
2020-02-29 13:18:15 +01:00
2021-04-22 13:00:00 +02:00
if ! resolveDependency ( v , disablePayloadAutoBinding , dest , funcDependencies ... ) {
2020-02-29 13:18:15 +01:00
panic ( fmt . Sprintf ( "bad value: could not resolve a dependency from: %#+v" , dependency ) )
}
return dest
}
// DependencyResolver func(v reflect.Value, dest *Dependency) bool
// Resolver DependencyResolver
2021-04-22 13:00:00 +02:00
func resolveDependency ( v reflect . Value , disablePayloadAutoBinding bool , dest * Dependency , funcDependencies ... * Dependency ) bool {
2020-02-29 13:18:15 +01:00
return fromDependencyHandler ( v , dest ) ||
fromStructValue ( v , dest ) ||
fromFunc ( v , dest ) ||
2021-04-22 13:00:00 +02:00
len ( funcDependencies ) > 0 && fromDependentFunc ( v , disablePayloadAutoBinding , dest , funcDependencies )
2020-02-29 13:18:15 +01:00
}
2020-06-21 16:15:28 +02:00
func fromDependencyHandler ( _ reflect . Value , dest * Dependency ) bool {
2020-02-29 13:18:15 +01:00
// It's already on the desired form, just return it.
dependency := dest . OriginalValue
handler , ok := dependency . ( DependencyHandler )
if ! ok {
2020-07-10 22:21:09 +02:00
handler , ok = dependency . ( func ( * context . Context , * Input ) ( reflect . Value , error ) )
2020-02-29 13:18:15 +01:00
if ! ok {
// It's almost a handler, only the second `Input` argument is missing.
2020-07-10 22:21:09 +02:00
if h , is := dependency . ( func ( * context . Context ) ( reflect . Value , error ) ) ; is {
handler = func ( ctx * context . Context , _ * Input ) ( reflect . Value , error ) {
2020-02-29 13:18:15 +01:00
return h ( ctx )
}
ok = is
}
}
}
if ! ok {
return false
}
dest . Handle = handler
return true
}
func fromStructValue ( v reflect . Value , dest * Dependency ) bool {
if ! isFunc ( v ) {
// It's just a static value.
2020-07-10 22:21:09 +02:00
handler := func ( * context . Context , * Input ) ( reflect . Value , error ) {
2020-02-29 13:18:15 +01:00
return v , nil
}
dest . DestType = v . Type ( )
dest . Static = true
dest . Handle = handler
return true
}
return false
}
func fromFunc ( v reflect . Value , dest * Dependency ) bool {
if ! isFunc ( v ) {
return false
}
typ := v . Type ( )
numIn := typ . NumIn ( )
numOut := typ . NumOut ( )
if numIn == 0 {
2022-06-05 21:12:57 +02:00
// it's an empty function, that must return a structure.
if numOut != 1 {
firstOutType := indirectType ( typ . Out ( 0 ) )
if firstOutType . Kind ( ) != reflect . Struct && firstOutType . Kind ( ) != reflect . Interface {
panic ( fmt . Sprintf ( "bad value: function has zero inputs: empty input function must output a single value but got: length=%v, type[0]=%s" , numOut , firstOutType . String ( ) ) )
}
}
// fallback to structure.
return fromStructValue ( v . Call ( nil ) [ 0 ] , dest )
2020-02-29 13:18:15 +01:00
}
if numOut == 0 {
panic ( "bad value: function has zero outputs" )
}
if numOut == 2 && ! isError ( typ . Out ( 1 ) ) {
panic ( "bad value: second output should be an error" )
}
if numOut > 2 {
// - at least one output value
// - maximum of two output values
// - second output value should be a type of error.
panic ( fmt . Sprintf ( "bad value: function has invalid number of output arguments: %v" , numOut ) )
}
var handler DependencyHandler
firstIsContext := isContext ( typ . In ( 0 ) )
secondIsInput := numIn == 2 && typ . In ( 1 ) == inputTyp
onlyContext := ( numIn == 1 && firstIsContext ) || ( numIn == 2 && firstIsContext && typ . IsVariadic ( ) )
if onlyContext || ( firstIsContext && secondIsInput ) {
handler = handlerFromFunc ( v , typ )
}
if handler == nil {
return false
}
dest . DestType = typ . Out ( 0 )
dest . Handle = handler
return true
}
func handlerFromFunc ( v reflect . Value , typ reflect . Type ) DependencyHandler {
// * func(Context, *Input) <T>, func(Context) <T>
// * func(Context) <T>, func(Context) <T>
// * func(Context, *Input) <T>, func(Context) (<T>, error)
// * func(Context) <T>, func(Context) (<T>, error)
hasErrorOut := typ . NumOut ( ) == 2 // if two, always an error type here.
hasInputIn := typ . NumIn ( ) == 2 && typ . In ( 1 ) == inputTyp
2020-07-10 22:21:09 +02:00
return func ( ctx * context . Context , input * Input ) ( reflect . Value , error ) {
2020-02-29 13:18:15 +01:00
inputs := ctx . ReflectValue ( )
if hasInputIn {
inputs = append ( inputs , input . selfValue )
}
results := v . Call ( inputs )
if hasErrorOut {
return results [ 0 ] , toError ( results [ 1 ] )
}
return results [ 0 ] , nil
}
}
2021-04-22 13:00:00 +02:00
func fromDependentFunc ( v reflect . Value , disablePayloadAutoBinding bool , dest * Dependency , funcDependencies [ ] * Dependency ) bool {
2020-02-29 13:18:15 +01:00
// * func(<D>...) returns <T>
// * func(<D>...) returns error
// * func(<D>...) returns <T>, error
typ := v . Type ( )
if ! isFunc ( v ) {
return false
}
2021-04-22 13:00:00 +02:00
bindings := getBindingsForFunc ( v , funcDependencies , disablePayloadAutoBinding , - 1 /* parameter bindings are disabled for depent dependencies */ )
2020-07-24 15:50:00 +02:00
2020-02-29 13:18:15 +01:00
numIn := typ . NumIn ( )
numOut := typ . NumOut ( )
2020-07-24 15:50:00 +02:00
// d1 = Logger
// d2 = func(Logger) S1
// d2 should be static: it accepts dependencies that are static
2021-02-21 21:24:01 +01:00
// (note: we don't check the output argument(s) of this dependency).
2020-07-24 15:50:00 +02:00
if numIn == len ( bindings ) {
static := true
for _ , b := range bindings {
2022-03-01 20:26:02 +01:00
if ! b . Dependency . Static && b . Dependency . Match ( typ . In ( b . Input . Index ) ) {
2020-07-24 15:50:00 +02:00
static = false
break
}
}
if static {
dest . Static = static
}
}
2020-02-29 13:18:15 +01:00
firstOutIsError := numOut == 1 && isError ( typ . Out ( 0 ) )
secondOutIsError := numOut == 2 && isError ( typ . Out ( 1 ) )
2020-07-10 22:21:09 +02:00
handler := func ( ctx * context . Context , _ * Input ) ( reflect . Value , error ) {
2020-02-29 13:18:15 +01:00
inputs := make ( [ ] reflect . Value , numIn )
for _ , binding := range bindings {
input , err := binding . Dependency . Handle ( ctx , binding . Input )
if err != nil {
if err == ErrSeeOther {
continue
}
return emptyValue , err
}
inputs [ binding . Input . Index ] = input
}
outputs := v . Call ( inputs )
if firstOutIsError {
return emptyValue , toError ( outputs [ 0 ] )
} else if secondOutIsError {
return outputs [ 0 ] , toError ( outputs [ 1 ] )
}
return outputs [ 0 ] , nil
}
dest . DestType = typ . Out ( 0 )
dest . Handle = handler
2020-07-24 15:50:00 +02:00
2020-02-29 13:18:15 +01:00
return true
}