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
|
|
|
|
}
|
|
|
|
|
2023-08-21 19:11:14 +02:00
|
|
|
type singletonStruct interface {
|
|
|
|
Singleton() bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func isMarkedAsSingleton(structPtr any) bool {
|
|
|
|
if sing, ok := structPtr.(singletonStruct); ok && sing.Singleton() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2023-08-21 19:11:14 +02:00
|
|
|
isSingleton := isMarkedAsSingleton(structPtr)
|
|
|
|
|
|
|
|
disablePayloadAutoBinding := c.DisablePayloadAutoBinding
|
|
|
|
enableStructDependents := c.EnableStructDependents
|
|
|
|
disableStructDynamicBindings := c.DisableStructDynamicBindings
|
|
|
|
if isSingleton {
|
|
|
|
disablePayloadAutoBinding = true
|
|
|
|
enableStructDependents = false
|
|
|
|
disableStructDynamicBindings = true
|
|
|
|
}
|
|
|
|
|
2020-02-29 13:18:15 +01:00
|
|
|
// get struct's fields bindings.
|
2023-08-21 19:11:14 +02:00
|
|
|
bindings := getBindingsForStruct(v, c.Dependencies, c.MarkExportedFieldsAsRequired, disablePayloadAutoBinding, enableStructDependents, c.DependencyMatcher, 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()
|
2023-07-08 01:08:18 +02:00
|
|
|
|
|
|
|
// fmt.Printf("makeStruct: bindings length = %d\n", len(bindings))
|
2020-02-29 13:18:15 +01:00
|
|
|
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 {
|
2023-08-21 19:11:14 +02:00
|
|
|
if disableStructDynamicBindings {
|
2022-03-12 11:32:27 +01:00
|
|
|
panic(fmt.Sprintf("binder: DisableStructDynamicBindings setting is set to true: dynamic binding found: %s", b.String()))
|
|
|
|
}
|
|
|
|
|
2020-02-29 13:18:15 +01:00
|
|
|
singleton = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-21 19:11:14 +02:00
|
|
|
if isSingleton && !singleton {
|
|
|
|
panic(fmt.Sprintf("binder: Singleton setting is set to true but struct has dynamic bindings: %s", typ))
|
|
|
|
}
|
|
|
|
|
2020-02-29 13:18:15 +01:00
|
|
|
s := &Struct{
|
|
|
|
ptrValue: v,
|
|
|
|
ptrType: typ,
|
|
|
|
elementType: elem.Type(),
|
|
|
|
bindings: bindings,
|
|
|
|
Singleton: singleton,
|
|
|
|
}
|
|
|
|
|
|
|
|
isErrHandler := isErrorHandler(typ)
|
|
|
|
newContainer := c.Clone()
|
2020-08-24 20:44:29 +02:00
|
|
|
newContainer.fillReport(typ.String(), bindings)
|
2020-02-29 13:18:15 +01:00
|
|
|
// 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.
|
2020-08-22 07:04:22 +02:00
|
|
|
newContainer.Register(s.Acquire).Explicitly().DestType = typ
|
2020-02-29 13:18:15 +01:00
|
|
|
|
2020-07-10 22:21:09 +02:00
|
|
|
newContainer.GetErrorHandler = func(ctx *context.Context) ErrorHandler {
|
2020-02-29 13:18:15 +01:00
|
|
|
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-07-10 22:21:09 +02:00
|
|
|
func (s *Struct) Acquire(ctx *context.Context) (reflect.Value, error) {
|
2020-02-29 13:18:15 +01:00
|
|
|
if s.Singleton {
|
|
|
|
ctx.Values().Set(context.ControllerContextKey, s.ptrValue)
|
|
|
|
return s.ptrValue, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrl := ctx.Controller()
|
2020-06-18 22:53:53 +02:00
|
|
|
if ctrl.Kind() == reflect.Invalid ||
|
|
|
|
ctrl.Type() != s.ptrType /* in case of changing controller in the same request (see RouteOverlap feature) */ {
|
2020-02-29 13:18:15 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-09-14 00:09:51 +02:00
|
|
|
s.Container.GetErrorHandler(ctx).HandleError(ctx, err)
|
|
|
|
|
|
|
|
if ctx.IsStopped() {
|
|
|
|
// return emptyValue, err
|
|
|
|
return ctrl, err
|
|
|
|
} // #1629
|
2020-02-29 13:18:15 +01:00
|
|
|
}
|
2020-09-14 00:09:51 +02:00
|
|
|
|
2020-02-29 13:18:15 +01:00
|
|
|
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
|
|
|
}
|