package di import ( "errors" "reflect" "github.com/kataras/iris/v12/context" ) // 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 ReturnValue func(ctx context.Context) reflect.Value } // 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. func MakeBindObject(v reflect.Value, goodFunc TypeChecker, errorHandler ErrorHandler) (b BindObject, err error) { if IsFunc(v) { b.BindType = Dynamic b.ReturnValue, b.Type, err = MakeReturnValue(v, goodFunc, errorHandler) } else { b.BindType = Static b.Type = v.Type() b.Value = v } return } 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`. // // The return type of the "fn" should be a value instance, not a pointer. // The binder function should return just one value. func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker, errorHandler ErrorHandler) (func(ctx context.Context) reflect.Value, reflect.Type, error) { typ := IndirectType(fn.Type()) // invalid if not a func. if typ.Kind() != reflect.Func { return nil, typ, errBad } 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)))) { return nil, typ, errBad } if goodFunc != nil { if !goodFunc(typ) { return nil, typ, errBad } } firstOutTyp := typ.Out(0) firstZeroOutVal := reflect.New(firstOutTyp).Elem() bf := func(ctx context.Context) reflect.Value { results := fn.Call(ctx.ReflectValue()) if n == 2 { // two, second is always error. errVal := results[1] if !errVal.IsNil() { if errorHandler != nil { errorHandler.HandleError(ctx, errVal.Interface().(error)) } return firstZeroOutVal } } v := results[0] if !v.IsValid() { // check the first value, second is error. return firstZeroOutVal } // if firstOutTyp == reflectValueType { // converted := v.Convert(typ.In(0)) // fmt.Printf("object.go#124: converted: %#+v\n", converted) // return converted //reflect.ValueOf(v.Interface()) // } // if v.String() == "" { // println("di/object.go: " + v.String()) // // println("di/object.go: because it's interface{} it should be returned as: " + v.Elem().Type().String() + " and its value: " + v.Elem().Interface().(string)) // return v.Elem() // } return v } return bf, firstOutTyp, nil } // 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. func (b *BindObject) Assign(ctx context.Context, toSetter func(reflect.Value)) { if b.BindType == Dynamic { toSetter(b.ReturnValue(ctx)) return } toSetter(b.Value) }