2017-12-25 19:05:32 +01:00
|
|
|
package di
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"reflect"
|
2020-02-14 22:34:56 +01:00
|
|
|
|
|
|
|
"github.com/kataras/iris/v12/context"
|
2017-12-25 19:05:32 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// BindType is the type of a binded object/value, it's being used to
|
|
|
|
// check if the value is accessible after a function call with a "ctx" when needed ( Dynamic type)
|
|
|
|
// or it's just a struct value (a service | Static type).
|
|
|
|
type BindType uint32
|
|
|
|
|
|
|
|
const (
|
|
|
|
// Static is the simple assignable value, a static value.
|
|
|
|
Static BindType = iota
|
|
|
|
// Dynamic returns a value but it depends on some input arguments from the caller,
|
|
|
|
// on serve time.
|
|
|
|
Dynamic
|
|
|
|
)
|
|
|
|
|
|
|
|
func bindTypeString(typ BindType) string {
|
|
|
|
switch typ {
|
|
|
|
case Dynamic:
|
|
|
|
return "Dynamic"
|
|
|
|
default:
|
|
|
|
return "Static"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// BindObject contains the dependency value's read-only information.
|
|
|
|
// FuncInjector and StructInjector keeps information about their
|
|
|
|
// input arguments/or fields, these properties contain a `BindObject` inside them.
|
|
|
|
type BindObject struct {
|
|
|
|
Type reflect.Type // the Type of 'Value' or the type of the returned 'ReturnValue' .
|
|
|
|
Value reflect.Value
|
|
|
|
|
|
|
|
BindType BindType
|
2020-02-14 22:34:56 +01:00
|
|
|
ReturnValue func(ctx context.Context) reflect.Value
|
2017-12-25 19:05:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// MakeBindObject accepts any "v" value, struct, pointer or a function
|
|
|
|
// and a type checker that is used to check if the fields (if "v.elem()" is struct)
|
|
|
|
// or the input arguments (if "v.elem()" is func)
|
|
|
|
// are valid to be included as the final object's dependencies, even if the caller added more
|
|
|
|
// the "di" is smart enough to select what each "v" needs and what not before serve time.
|
2020-02-16 10:14:35 +01:00
|
|
|
func MakeBindObject(v reflect.Value, errorHandler ErrorHandler) (b BindObject, err error) {
|
2017-12-25 19:05:32 +01:00
|
|
|
if IsFunc(v) {
|
|
|
|
b.BindType = Dynamic
|
2020-02-16 10:14:35 +01:00
|
|
|
b.ReturnValue, b.Type, err = MakeReturnValue(v, errorHandler)
|
2017-12-25 19:05:32 +01:00
|
|
|
} else {
|
|
|
|
b.BindType = Static
|
|
|
|
b.Type = v.Type()
|
|
|
|
b.Value = v
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-02-16 10:14:35 +01:00
|
|
|
func tryBindContext(fieldOrFuncInput reflect.Type) (*BindObject, bool) {
|
|
|
|
if !IsContext(fieldOrFuncInput) {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
// this is being used on both func injector and struct injector.
|
|
|
|
// if the func's input argument or the struct's field is a type of Context
|
|
|
|
// then we can do a fast binding using the ctxValue
|
|
|
|
// which is used as slice of reflect.Value, because of the final method's `Call`.
|
|
|
|
return &BindObject{
|
|
|
|
Type: contextTyp,
|
|
|
|
BindType: Dynamic,
|
|
|
|
ReturnValue: func(ctx context.Context) reflect.Value {
|
|
|
|
return ctx.ReflectValue()[0]
|
|
|
|
},
|
|
|
|
}, true
|
|
|
|
}
|
|
|
|
|
2017-12-25 19:05:32 +01:00
|
|
|
var errBad = errors.New("bad")
|
|
|
|
|
|
|
|
// MakeReturnValue takes any function
|
|
|
|
// that accept custom values and returns something,
|
|
|
|
// it returns a binder function, which accepts a slice of reflect.Value
|
|
|
|
// and returns a single one reflect.Value for that.
|
|
|
|
// It's being used to resolve the input parameters on a "x" consumer faster.
|
|
|
|
//
|
|
|
|
// The "fn" can have the following form:
|
|
|
|
// `func(myService) MyViewModel`.
|
|
|
|
//
|
2020-02-14 22:34:56 +01:00
|
|
|
// The return type of the "fn" should be a value instance, not a pointer.
|
|
|
|
// The binder function should return just one value.
|
2020-02-16 10:14:35 +01:00
|
|
|
func MakeReturnValue(fn reflect.Value, errorHandler ErrorHandler) (func(context.Context) reflect.Value, reflect.Type, error) {
|
2017-12-25 19:05:32 +01:00
|
|
|
typ := IndirectType(fn.Type())
|
|
|
|
|
|
|
|
// invalid if not a func.
|
|
|
|
if typ.Kind() != reflect.Func {
|
|
|
|
return nil, typ, errBad
|
|
|
|
}
|
|
|
|
|
2019-02-15 23:42:26 +01:00
|
|
|
n := typ.NumOut()
|
|
|
|
|
|
|
|
// invalid if not returns one single value or two values but the second is not an error.
|
|
|
|
if !(n == 1 || (n == 2 && IsError(typ.Out(1)))) {
|
2017-12-25 19:05:32 +01:00
|
|
|
return nil, typ, errBad
|
|
|
|
}
|
|
|
|
|
2020-02-16 10:14:35 +01:00
|
|
|
if !goodFunc(typ) {
|
|
|
|
return nil, typ, errBad
|
2017-12-25 19:05:32 +01:00
|
|
|
}
|
|
|
|
|
2019-02-15 23:42:26 +01:00
|
|
|
firstOutTyp := typ.Out(0)
|
|
|
|
firstZeroOutVal := reflect.New(firstOutTyp).Elem()
|
2017-12-25 19:05:32 +01:00
|
|
|
|
2020-02-14 22:34:56 +01:00
|
|
|
bf := func(ctx context.Context) reflect.Value {
|
|
|
|
results := fn.Call(ctx.ReflectValue())
|
2019-02-15 23:42:26 +01:00
|
|
|
if n == 2 {
|
|
|
|
// two, second is always error.
|
|
|
|
errVal := results[1]
|
|
|
|
if !errVal.IsNil() {
|
2020-02-14 22:34:56 +01:00
|
|
|
if errorHandler != nil {
|
|
|
|
errorHandler.HandleError(ctx, errVal.Interface().(error))
|
2019-02-15 23:42:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return firstZeroOutVal
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-14 22:34:56 +01:00
|
|
|
v := results[0]
|
|
|
|
if !v.IsValid() { // check the first value, second is error.
|
|
|
|
return firstZeroOutVal
|
|
|
|
}
|
|
|
|
|
2017-12-25 19:05:32 +01:00
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2019-02-15 23:42:26 +01:00
|
|
|
return bf, firstOutTyp, nil
|
2017-12-25 19:05:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// IsAssignable checks if "to" type can be used as "b.Value/ReturnValue".
|
|
|
|
func (b *BindObject) IsAssignable(to reflect.Type) bool {
|
|
|
|
return equalTypes(b.Type, to)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assign sets the values to a setter, "toSetter" contains the setter, so the caller
|
|
|
|
// can use it for multiple and different structs/functions as well.
|
2020-02-14 22:34:56 +01:00
|
|
|
func (b *BindObject) Assign(ctx context.Context, toSetter func(reflect.Value)) {
|
2017-12-25 19:05:32 +01:00
|
|
|
if b.BindType == Dynamic {
|
|
|
|
toSetter(b.ReturnValue(ctx))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
toSetter(b.Value)
|
|
|
|
}
|