1
0
mirror of https://github.com/kataras/iris.git synced 2026-01-28 14:25:57 +00:00

fix https://github.com/kataras/iris/issues/1450 and continue on implementing 1449

Former-commit-id: 617f64d061e88f050a443ea1751fa244164656c5
This commit is contained in:
Gerasimos (Makis) Maropoulos
2020-02-14 23:34:56 +02:00
parent 09a410c6cb
commit c13fd84354
24 changed files with 581 additions and 144 deletions

View File

@@ -1,8 +1,12 @@
// Package di provides dependency injection for the Iris Hero and Iris MVC new features.
// It's used internally by "hero" and "mvc" packages directly.
// It's used internally by "hero" and "mvc" packages.
package di
import "reflect"
import (
"reflect"
"github.com/kataras/iris/v12/context"
)
type (
// Hijacker is a type which is used to catch fields or function's input argument
@@ -11,13 +15,33 @@ type (
// TypeChecker checks if a specific field's or function input argument's
// is valid to be binded.
TypeChecker func(reflect.Type) bool
// ErrorHandler is the optional interface to handle errors per hero func,
// see `mvc/Application#HandleError` for MVC application-level error handler registration too.
//
// Handles non-nil errors return from a hero handler or a controller's method (see `DispatchFuncResult`)
// and (from v12.1.8) the error may return from a request-scoped dynamic dependency (see `MakeReturnValue`).
ErrorHandler interface {
HandleError(ctx context.Context, err error)
}
// ErrorHandlerFunc implements the `ErrorHandler`.
// It describes the type defnition for an error handler.
ErrorHandlerFunc func(ctx context.Context, err error)
)
// HandleError fires when the `DispatchFuncResult` or `MakereturnValue` return a non-nil error.
func (fn ErrorHandlerFunc) HandleError(ctx context.Context, err error) {
fn(ctx, err)
}
var (
// DefaultHijacker is the hijacker used on the package-level Struct & Func functions.
DefaultHijacker Hijacker
// DefaultTypeChecker is the typechecker used on the package-level Struct & Func functions.
DefaultTypeChecker TypeChecker
// DefaultErrorHandler is the error handler used on the package-level `Func` function
// to catch any errors from dependencies or handlers.
DefaultErrorHandler ErrorHandler
)
// Struct is being used to return a new injector based on
@@ -26,7 +50,7 @@ var (
// with the injector's `Inject` and `InjectElem` methods.
func Struct(s interface{}, values ...reflect.Value) *StructInjector {
if s == nil {
return &StructInjector{Has: false}
return &StructInjector{}
}
return MakeStructInjector(
@@ -45,13 +69,14 @@ func Struct(s interface{}, values ...reflect.Value) *StructInjector {
// with the injector's `Inject` method.
func Func(fn interface{}, values ...reflect.Value) *FuncInjector {
if fn == nil {
return &FuncInjector{Has: false}
return &FuncInjector{}
}
return MakeFuncInjector(
ValueOf(fn),
DefaultHijacker,
DefaultTypeChecker,
DefaultErrorHandler,
values...,
)
}
@@ -63,9 +88,10 @@ func Func(fn interface{}, values ...reflect.Value) *FuncInjector {
type D struct {
Values
hijacker Hijacker
goodFunc TypeChecker
sorter Sorter
hijacker Hijacker
goodFunc TypeChecker
errorHandler ErrorHandler
sorter Sorter
}
// New creates and returns a new Dependency Injection container.
@@ -87,6 +113,13 @@ func (d *D) GoodFunc(fn TypeChecker) *D {
return d
}
// ErrorHandler adds a handler which will be fired when a handler's second output argument is error and it's not nil
// or when a request-scoped dynamic function dependency's second output argument is error and it's not nil.
func (d *D) ErrorHandler(errorHandler ErrorHandler) *D {
d.errorHandler = errorHandler
return d
}
// Sort sets the fields and valid bindable values sorter for struct injection.
func (d *D) Sort(with Sorter) *D {
d.sorter = with
@@ -97,10 +130,11 @@ func (d *D) Sort(with Sorter) *D {
// parent's (current "D") hijacker, good func type checker, sorter and all dependencies values.
func (d *D) Clone() *D {
return &D{
Values: d.Values.Clone(),
hijacker: d.hijacker,
goodFunc: d.goodFunc,
sorter: d.sorter,
Values: d.Values.Clone(),
hijacker: d.hijacker,
goodFunc: d.goodFunc,
errorHandler: d.errorHandler,
sorter: d.sorter,
}
}
@@ -136,6 +170,7 @@ func (d *D) Func(fn interface{}) *FuncInjector {
ValueOf(fn),
d.hijacker,
d.goodFunc,
d.errorHandler,
d.Values...,
)
).ErrorHandler(d.errorHandler)
}

View File

@@ -3,6 +3,8 @@ package di
import (
"fmt"
"reflect"
"github.com/kataras/iris/v12/context"
)
type (
@@ -16,9 +18,10 @@ type (
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
fn reflect.Value
typ reflect.Type
goodFunc TypeChecker
errorHandler ErrorHandler
inputs []*targetFuncInput
// Length is the number of the valid, final binded input arguments.
@@ -32,13 +35,15 @@ type (
)
type missingInput struct {
index int // the function's input argument's index.
found bool
index int // the function's input argument's index.
found bool
remaining Values
}
func (s *FuncInjector) miss(index int) {
func (s *FuncInjector) miss(index int, remaining Values) {
s.lost = append(s.lost, &missingInput{
index: index,
index: index,
remaining: remaining,
})
}
@@ -46,12 +51,13 @@ func (s *FuncInjector) miss(index int) {
// 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 {
func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, errorHandler ErrorHandler, values ...reflect.Value) *FuncInjector {
typ := IndirectType(fn.Type())
s := &FuncInjector{
fn: fn,
typ: typ,
goodFunc: goodFunc,
fn: fn,
typ: typ,
goodFunc: goodFunc,
errorHandler: errorHandler,
}
if !IsFunc(typ) {
@@ -100,7 +106,7 @@ func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, v
// 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)
s.miss(i, values) // send the remaining dependencies values.
}
}
@@ -123,11 +129,28 @@ func (s *FuncInjector) addValue(inputIndex int, value reflect.Value) bool {
inTyp := s.typ.In(inputIndex)
// the binded values to the func's inputs.
b, err := MakeBindObject(value, s.goodFunc)
b, err := MakeBindObject(value, s.goodFunc, s.errorHandler)
if err != nil {
return false
}
// TODO: expose that (need to push a fix for issue #1450 first)
if b.Type == reflectValueType {
b.Type = inTyp
// returnValue := b.ReturnValue
b.ReturnValue = func(ctx context.Context) reflect.Value {
newValue := reflect.New(inTyp)
if err := ctx.ReadJSON(newValue.Interface()); err != nil {
if s.errorHandler != nil {
s.errorHandler.HandleError(ctx, err)
}
}
return newValue.Elem()
}
}
if b.IsAssignable(inTyp) {
// fmt.Printf("binded input index: %d for type: %s and value: %v with pointer: %v\n",
// i, b.Type.String(), inTyp.String(), inTyp.Pointer())
@@ -141,9 +164,15 @@ func (s *FuncInjector) addValue(inputIndex int, value reflect.Value) bool {
return false
}
// ErrorHandler registers an error handler for this FuncInjector.
func (s *FuncInjector) ErrorHandler(errorHandler ErrorHandler) *FuncInjector {
s.errorHandler = errorHandler
return s
}
// Retry used to add missing dependencies, i.e path parameter builtin bindings if not already exists
// in the `hero.Handler`, once, only for that func injector.
func (s *FuncInjector) Retry(retryFn func(inIndex int, inTyp reflect.Type) (reflect.Value, bool)) bool {
func (s *FuncInjector) Retry(retryFn func(inIndex int, inTyp reflect.Type, remainingValues Values) (reflect.Value, bool)) bool {
for _, missing := range s.lost {
if missing.found {
continue
@@ -152,7 +181,7 @@ func (s *FuncInjector) Retry(retryFn func(inIndex int, inTyp reflect.Type) (refl
invalidIndex := missing.index
inTyp := s.typ.In(invalidIndex)
v, ok := retryFn(invalidIndex, inTyp)
v, ok := retryFn(invalidIndex, inTyp, missing.remaining)
if !ok {
continue
}
@@ -186,12 +215,13 @@ func (s *FuncInjector) String() (trace string) {
// 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) {
func (s *FuncInjector) Inject(ctx context.Context, in *[]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 of type: %s\n",
// input.InputIndex, v.String(), v.Type().Name())
args[input.InputIndex] = v
})
}
@@ -205,8 +235,8 @@ func (s *FuncInjector) Inject(in *[]reflect.Value, ctx ...reflect.Value) {
// 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 {
func (s *FuncInjector) Call(ctx context.Context) []reflect.Value {
in := make([]reflect.Value, s.Length)
s.Inject(&in, ctx...)
s.Inject(ctx, &in)
return s.fn.Call(in)
}

View File

@@ -3,6 +3,8 @@ 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
@@ -35,7 +37,7 @@ type BindObject struct {
Value reflect.Value
BindType BindType
ReturnValue func([]reflect.Value) reflect.Value
ReturnValue func(ctx context.Context) reflect.Value
}
// MakeBindObject accepts any "v" value, struct, pointer or a function
@@ -43,10 +45,10 @@ type BindObject 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) (b BindObject, err error) {
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)
b.ReturnValue, b.Type, err = MakeReturnValue(v, goodFunc, errorHandler)
} else {
b.BindType = Static
b.Type = v.Type()
@@ -67,9 +69,9 @@ var errBad = errors.New("bad")
// The "fn" can have the following form:
// `func(myService) MyViewModel`.
//
// The return type of the "fn" should be a value instance, not a pointer, for your own protection.
// The binder function should return only one value.
func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker) (func([]reflect.Value) reflect.Value, reflect.Type, error) {
// 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.
@@ -93,32 +95,30 @@ func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker) (func([]reflect.Val
firstOutTyp := typ.Out(0)
firstZeroOutVal := reflect.New(firstOutTyp).Elem()
bf := func(ctxValue []reflect.Value) reflect.Value {
results := fn.Call(ctxValue)
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 n == 2 {
// two, second is always error.
errVal := results[1]
if !errVal.IsNil() {
// error is not nil, do something with it.
if ctx, ok := ctxValue[0].Interface().(interface {
StatusCode(int)
WriteString(string) (int, error)
StopExecution()
}); ok {
ctx.StatusCode(400)
ctx.WriteString(errVal.Interface().(error).Error())
ctx.StopExecution()
}
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() == "<interface {} Value>" {
// println("di/object.go: " + v.String())
@@ -138,7 +138,7 @@ func (b *BindObject) IsAssignable(to reflect.Type) bool {
// 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 []reflect.Value, toSetter func(reflect.Value)) {
func (b *BindObject) Assign(ctx context.Context, toSetter func(reflect.Value)) {
if b.BindType == Dynamic {
toSetter(b.ReturnValue(ctx))
return

View File

@@ -138,10 +138,14 @@ func IsFunc(kindable interface {
return kindable.Kind() == reflect.Func
}
var reflectValueType = reflect.TypeOf(reflect.Value{})
func equalTypes(got reflect.Type, expected reflect.Type) bool {
if got == expected {
return true
}
// fmt.Printf("got: %s expected: %s\n", got.String(), expected.String())
// if accepts an interface, check if the given "got" type does
// implement this "expected" user handler's input argument.
if expected.Kind() == reflect.Interface {
@@ -151,10 +155,6 @@ func equalTypes(got reflect.Type, expected reflect.Type) bool {
return got.AssignableTo(expected)
}
// if got.String() == "interface {}" {
// return true
// }
return false
}

View File

@@ -4,6 +4,8 @@ import (
"fmt"
"reflect"
"sort"
"github.com/kataras/iris/v12/context"
)
// Scope is the struct injector's struct value scope/permant state.
@@ -155,7 +157,7 @@ func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker,
}
// the binded values to the struct's fields.
b, err := MakeBindObject(val, goodFunc)
b, err := MakeBindObject(val, goodFunc, nil)
if err != nil {
return s // if error stop here.
}
@@ -288,17 +290,17 @@ func (s *StructInjector) String() (trace string) {
// Inject accepts a destination struct and any optional context value(s),
// hero and mvc takes only one context value and this is the `context.Context`.
// It applies the bindings to the "dest" struct. It calls the InjectElem.
func (s *StructInjector) Inject(dest interface{}, ctx ...reflect.Value) {
func (s *StructInjector) Inject(ctx context.Context, dest interface{}) {
if dest == nil {
return
}
v := IndirectValue(ValueOf(dest))
s.InjectElem(v, ctx...)
s.InjectElem(ctx, v)
}
// InjectElem same as `Inject` but accepts a reflect.Value and bind the necessary fields directly.
func (s *StructInjector) InjectElem(destElem reflect.Value, ctx ...reflect.Value) {
func (s *StructInjector) InjectElem(ctx context.Context, destElem reflect.Value) {
for _, f := range s.fields {
f.Object.Assign(ctx, func(v reflect.Value) {
ff := destElem.FieldByIndex(f.FieldIndex)