2020-02-29 13:18:15 +01:00
|
|
|
package hero
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
|
|
|
|
"github.com/kataras/iris/v12/context"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Sorter is the type for sort customization of a struct's fields
|
|
|
|
// and its available bindable values.
|
|
|
|
//
|
|
|
|
// Sorting applies only when a field can accept more than one registered value.
|
|
|
|
type Sorter func(t1 reflect.Type, t2 reflect.Type) bool
|
|
|
|
|
|
|
|
// sortByNumMethods is a builtin sorter to sort fields and values
|
|
|
|
// based on their type and its number of methods, highest number of methods goes first.
|
|
|
|
//
|
|
|
|
// It is the default sorter on struct injector of `hero.Struct` method.
|
|
|
|
var sortByNumMethods Sorter = func(t1 reflect.Type, t2 reflect.Type) bool {
|
|
|
|
if t1.Kind() != t2.Kind() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
if k := t1.Kind(); k == reflect.Interface || k == reflect.Struct {
|
|
|
|
return t1.NumMethod() > t2.NumMethod()
|
|
|
|
} else if k != reflect.Struct {
|
|
|
|
return false // non-structs goes last.
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-03-01 07:34:53 +01:00
|
|
|
// Struct keeps a record of a particular struct value injection.
|
|
|
|
// See `Container.Struct` and `mvc#Application.Handle` methods.
|
2020-02-29 13:18:15 +01:00
|
|
|
type Struct struct {
|
|
|
|
ptrType reflect.Type
|
|
|
|
ptrValue reflect.Value // the original ptr struct value.
|
|
|
|
elementType reflect.Type // the original struct type.
|
2020-03-01 07:34:53 +01:00
|
|
|
bindings []*binding // struct field bindings.
|
2020-02-29 13:18:15 +01:00
|
|
|
|
|
|
|
Container *Container
|
|
|
|
Singleton bool
|
|
|
|
}
|
|
|
|
|
2020-03-02 18:48:53 +01:00
|
|
|
func makeStruct(structPtr interface{}, c *Container, partyParamsCount int) *Struct {
|
2020-02-29 13:18:15 +01:00
|
|
|
v := valueOf(structPtr)
|
|
|
|
typ := v.Type()
|
|
|
|
if typ.Kind() != reflect.Ptr || indirectType(typ).Kind() != reflect.Struct {
|
|
|
|
panic("binder: struct: should be a pointer to a struct value")
|
|
|
|
}
|
|
|
|
|
|
|
|
// get struct's fields bindings.
|
2020-03-02 18:48:53 +01:00
|
|
|
bindings := getBindingsForStruct(v, c.Dependencies, partyParamsCount, c.Sorter)
|
2020-02-29 13:18:15 +01:00
|
|
|
|
|
|
|
// length bindings of 0, means that it has no fields or all mapped deps are static.
|
|
|
|
// If static then Struct.Acquire will return the same "value" instance, otherwise it will create a new one.
|
|
|
|
singleton := true
|
|
|
|
elem := v.Elem()
|
|
|
|
for _, b := range bindings {
|
|
|
|
if b.Dependency.Static {
|
|
|
|
// Fill now.
|
|
|
|
input, err := b.Dependency.Handle(nil, b.Input)
|
|
|
|
if err != nil {
|
|
|
|
if err == ErrSeeOther {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
elem.FieldByIndex(b.Input.StructFieldIndex).Set(input)
|
|
|
|
} else if !b.Dependency.Static {
|
|
|
|
singleton = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
s := &Struct{
|
|
|
|
ptrValue: v,
|
|
|
|
ptrType: typ,
|
|
|
|
elementType: elem.Type(),
|
|
|
|
bindings: bindings,
|
|
|
|
Singleton: singleton,
|
|
|
|
}
|
|
|
|
|
|
|
|
isErrHandler := isErrorHandler(typ)
|
|
|
|
|
|
|
|
newContainer := c.Clone()
|
|
|
|
// Add the controller dependency itself as func dependency but with a known type which should be explicit binding
|
|
|
|
// in order to keep its maximum priority.
|
|
|
|
newContainer.Register(s.Acquire).
|
|
|
|
Explicitly().
|
|
|
|
DestType = typ
|
|
|
|
|
|
|
|
newContainer.GetErrorHandler = func(ctx context.Context) ErrorHandler {
|
|
|
|
if isErrHandler {
|
|
|
|
return ctx.Controller().Interface().(ErrorHandler)
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.GetErrorHandler(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
s.Container = newContainer
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2020-03-01 07:34:53 +01:00
|
|
|
// Acquire returns a struct value based on the request.
|
|
|
|
// If the dependencies are all static then these are already set-ed at the initialization of this Struct
|
|
|
|
// and the same struct value instance will be returned, ignoring the Context. Otherwise
|
|
|
|
// a new struct value with filled fields by its pre-calculated bindings will be returned instead.
|
2020-02-29 13:18:15 +01:00
|
|
|
func (s *Struct) Acquire(ctx context.Context) (reflect.Value, error) {
|
|
|
|
if s.Singleton {
|
|
|
|
ctx.Values().Set(context.ControllerContextKey, s.ptrValue)
|
|
|
|
return s.ptrValue, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrl := ctx.Controller()
|
|
|
|
if ctrl.Kind() == reflect.Invalid {
|
|
|
|
ctrl = reflect.New(s.elementType)
|
|
|
|
ctx.Values().Set(context.ControllerContextKey, ctrl)
|
|
|
|
elem := ctrl.Elem()
|
|
|
|
for _, b := range s.bindings {
|
|
|
|
input, err := b.Dependency.Handle(ctx, b.Input)
|
|
|
|
if err != nil {
|
|
|
|
if err == ErrSeeOther {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// return emptyValue, err
|
|
|
|
return ctrl, err
|
|
|
|
}
|
|
|
|
elem.FieldByIndex(b.Input.StructFieldIndex).Set(input)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ctrl, nil
|
|
|
|
}
|
|
|
|
|
2020-03-01 07:34:53 +01:00
|
|
|
// MethodHandler accepts a "methodName" that should be a valid an exported
|
|
|
|
// method of the struct and returns its converted Handler.
|
2020-03-02 18:48:53 +01:00
|
|
|
//
|
|
|
|
// Second input is optional,
|
|
|
|
// even zero is a valid value and can resolve path parameters correctly if from root party.
|
|
|
|
func (s *Struct) MethodHandler(methodName string, paramsCount int) context.Handler {
|
2020-02-29 13:18:15 +01:00
|
|
|
m, ok := s.ptrValue.Type().MethodByName(methodName)
|
|
|
|
if !ok {
|
|
|
|
panic(fmt.Sprintf("struct: method: %s does not exist", methodName))
|
|
|
|
}
|
|
|
|
|
2020-03-02 18:48:53 +01:00
|
|
|
return makeHandler(m.Func, s.Container, paramsCount)
|
2020-02-29 13:18:15 +01:00
|
|
|
}
|