mirror of
https://github.com/kataras/iris.git
synced 2026-01-04 18:57:03 +00:00
create a new package, name it as hero, I was thinking super or superb but hero is better name for what it does - the goal is to split the new 'mvc handlers' from the mvc system because they are not the same, users should know that they can use these type of rich binded handlers without controllers as well, like a normal handler and that I implemented here, the old files exist on the mvc package but will be removed at the next commit, I have to decide if we want type aliases for Result or no
Former-commit-id: cb775edc72bedc88aeab4c5a6de6bfc6bd56fae2
This commit is contained in:
215
hero/di/func.go
Normal file
215
hero/di/func.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package di
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type (
|
||||
targetFuncInput struct {
|
||||
Object *BindObject
|
||||
InputIndex int
|
||||
}
|
||||
|
||||
// FuncInjector keeps the data that are needed in order to do the binding injection
|
||||
// as fast as possible and with the best possible and safest way.
|
||||
FuncInjector struct {
|
||||
// the original function, is being used
|
||||
// only the .Call, which is referring to the same function, always.
|
||||
fn reflect.Value
|
||||
typ reflect.Type
|
||||
goodFunc TypeChecker
|
||||
|
||||
inputs []*targetFuncInput
|
||||
// Length is the number of the valid, final binded input arguments.
|
||||
Length int
|
||||
// Valid is True when `Length` is > 0, it's statically set-ed for
|
||||
// performance reasons.
|
||||
Has bool
|
||||
|
||||
trace string // for debug info.
|
||||
|
||||
lost []*missingInput // Author's note: don't change this to a map.
|
||||
}
|
||||
)
|
||||
|
||||
type missingInput struct {
|
||||
index int // the function's input argument's index.
|
||||
found bool
|
||||
}
|
||||
|
||||
func (s *FuncInjector) miss(index int) {
|
||||
s.lost = append(s.lost, &missingInput{
|
||||
index: index,
|
||||
})
|
||||
}
|
||||
|
||||
// MakeFuncInjector returns a new func injector, which will be the object
|
||||
// that the caller should use to bind input arguments of the "fn" function.
|
||||
//
|
||||
// The hijack and the goodFunc are optional, the "values" is the dependencies collection.
|
||||
func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *FuncInjector {
|
||||
typ := IndirectType(fn.Type())
|
||||
s := &FuncInjector{
|
||||
fn: fn,
|
||||
typ: typ,
|
||||
goodFunc: goodFunc,
|
||||
}
|
||||
|
||||
if !IsFunc(typ) {
|
||||
return s
|
||||
}
|
||||
|
||||
defer s.refresh()
|
||||
|
||||
n := typ.NumIn()
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
inTyp := typ.In(i)
|
||||
|
||||
if hijack != nil {
|
||||
b, ok := hijack(inTyp)
|
||||
|
||||
if ok && b != nil {
|
||||
s.inputs = append(s.inputs, &targetFuncInput{
|
||||
InputIndex: i,
|
||||
Object: b,
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
matched := false
|
||||
|
||||
for j, v := range values {
|
||||
if s.addValue(i, v) {
|
||||
matched = true
|
||||
// remove this value, so it will not try to get binded
|
||||
// again, a next value even with the same type is able to be
|
||||
// used to other input arg. One value per input argument, order
|
||||
// matters if same type of course.
|
||||
//if len(values) > j+1 {
|
||||
values = append(values[:j], values[j+1:]...)
|
||||
//}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !matched {
|
||||
// if no binding for this input argument,
|
||||
// this will make the func injector invalid state,
|
||||
// but before this let's make a list of failed
|
||||
// inputs, so they can be used for a re-try
|
||||
// with different set of binding "values".
|
||||
s.miss(i)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *FuncInjector) refresh() {
|
||||
s.Length = len(s.inputs)
|
||||
s.Has = s.Length > 0
|
||||
}
|
||||
|
||||
func (s *FuncInjector) addValue(inputIndex int, value reflect.Value) bool {
|
||||
defer s.refresh()
|
||||
|
||||
if s.typ.NumIn() < inputIndex {
|
||||
return false
|
||||
}
|
||||
|
||||
inTyp := s.typ.In(inputIndex)
|
||||
|
||||
// the binded values to the func's inputs.
|
||||
b, err := MakeBindObject(value, s.goodFunc)
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if b.IsAssignable(inTyp) {
|
||||
// println(inTyp.String() + " is assignable to " + val.Type().String())
|
||||
// fmt.Printf("binded input index: %d for type: %s and value: %v with pointer: %v\n",
|
||||
// i, b.Type.String(), value.String(), val.Pointer())
|
||||
s.inputs = append(s.inputs, &targetFuncInput{
|
||||
InputIndex: inputIndex,
|
||||
Object: &b,
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *FuncInjector) Retry(retryFn func(inIndex int, inTyp reflect.Type) (reflect.Value, bool)) bool {
|
||||
for _, missing := range s.lost {
|
||||
if missing.found {
|
||||
continue
|
||||
}
|
||||
|
||||
invalidIndex := missing.index
|
||||
|
||||
inTyp := s.typ.In(invalidIndex)
|
||||
v, ok := retryFn(invalidIndex, inTyp)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if !s.addValue(invalidIndex, v) {
|
||||
continue
|
||||
}
|
||||
|
||||
// if this value completes an invalid index
|
||||
// then remove this from the invalid input indexes.
|
||||
missing.found = true
|
||||
}
|
||||
|
||||
return s.Length == s.typ.NumIn()
|
||||
}
|
||||
|
||||
// String returns a debug trace text.
|
||||
func (s *FuncInjector) String() (trace string) {
|
||||
for i, in := range s.inputs {
|
||||
bindmethodTyp := bindTypeString(in.Object.BindType)
|
||||
typIn := s.typ.In(in.InputIndex)
|
||||
// remember: on methods that are part of a struct (i.e controller)
|
||||
// the input index = 1 is the begggining instead of the 0,
|
||||
// because the 0 is the controller receiver pointer of the method.
|
||||
trace += fmt.Sprintf("[%d] %s binding: '%s' for input position: %d and type: '%s'\n",
|
||||
i+1, bindmethodTyp, in.Object.Type.String(), in.InputIndex, typIn.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Inject accepts an already created slice of input arguments
|
||||
// and fills them, the "ctx" is optional and it's used
|
||||
// on the dependencies that depends on one or more input arguments, these are the "ctx".
|
||||
func (s *FuncInjector) Inject(in *[]reflect.Value, ctx ...reflect.Value) {
|
||||
args := *in
|
||||
for _, input := range s.inputs {
|
||||
input.Object.Assign(ctx, func(v reflect.Value) {
|
||||
// fmt.Printf("assign input index: %d for value: %v\n",
|
||||
// input.InputIndex, v.String())
|
||||
args[input.InputIndex] = v
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
*in = args
|
||||
}
|
||||
|
||||
// Call calls the "Inject" with a new slice of input arguments
|
||||
// that are computed by the length of the input argument from the MakeFuncInjector's "fn" function.
|
||||
//
|
||||
// If the function needs a receiver, so
|
||||
// the caller should be able to in[0] = receiver before injection,
|
||||
// then the `Inject` method should be used instead.
|
||||
func (s *FuncInjector) Call(ctx ...reflect.Value) []reflect.Value {
|
||||
in := make([]reflect.Value, s.Length, s.Length)
|
||||
s.Inject(&in, ctx...)
|
||||
return s.fn.Call(in)
|
||||
}
|
||||
Reference in New Issue
Block a user