2020-02-29 13:18:15 +01:00
package hero
import (
"fmt"
"reflect"
"sort"
"github.com/kataras/iris/v12/context"
)
2020-03-01 07:34:53 +01:00
// binding contains the Dependency and the Input, it's the result of a function or struct + dependencies.
type binding struct {
2020-02-29 13:18:15 +01:00
Dependency * Dependency
Input * Input
}
2020-03-01 07:34:53 +01:00
// Input contains the input reference of which a dependency is binded to.
2020-02-29 13:18:15 +01:00
type Input struct {
2020-08-24 20:44:29 +02:00
Index int // for func inputs
StructFieldIndex [ ] int // for struct fields in order to support embedded ones.
StructFieldName string // the struct field's name.
2020-02-29 13:18:15 +01:00
Type reflect . Type
selfValue reflect . Value // reflect.ValueOf(*Input) cache.
}
func newInput ( typ reflect . Type , index int , structFieldIndex [ ] int ) * Input {
in := & Input {
Index : index ,
StructFieldIndex : structFieldIndex ,
Type : typ ,
}
in . selfValue = reflect . ValueOf ( in )
return in
}
2020-08-24 20:44:29 +02:00
func newStructFieldInput ( f reflect . StructField ) * Input {
input := newInput ( f . Type , f . Index [ 0 ] , f . Index )
input . StructFieldName = f . Name
return input
}
2020-03-01 07:34:53 +01:00
// String returns the string representation of a binding.
func ( b * binding ) String ( ) string {
2020-02-29 13:18:15 +01:00
index := fmt . Sprintf ( "%d" , b . Input . Index )
if len ( b . Input . StructFieldIndex ) > 0 {
for j , i := range b . Input . StructFieldIndex {
if j == 0 {
index = fmt . Sprintf ( "%d" , i )
continue
}
index += fmt . Sprintf ( ".%d" , i )
}
}
return fmt . Sprintf ( "[%s:%s] maps to [%s]" , index , b . Input . Type . String ( ) , b . Dependency )
}
2020-03-01 07:34:53 +01:00
// Equal compares "b" and "other" bindings and reports whether they are referring to the same values.
func ( b * binding ) Equal ( other * binding ) bool {
2020-02-29 13:18:15 +01:00
if b == nil {
return other == nil
}
if other == nil {
return false
}
// if b.String() != other.String() {
// return false
// }
if expected , got := b . Dependency != nil , other . Dependency != nil ; expected != got {
return false
}
if expected , got := fmt . Sprintf ( "%v" , b . Dependency . OriginalValue ) , fmt . Sprintf ( "%v" , other . Dependency . OriginalValue ) ; expected != got {
return false
}
if expected , got := b . Dependency . DestType != nil , other . Dependency . DestType != nil ; expected != got {
return false
}
if b . Dependency . DestType != nil {
if expected , got := b . Dependency . DestType . String ( ) , other . Dependency . DestType . String ( ) ; expected != got {
return false
}
}
if expected , got := b . Input != nil , other . Input != nil ; expected != got {
return false
}
if b . Input != nil {
if expected , got := b . Input . Index , other . Input . Index ; expected != got {
return false
}
if expected , got := b . Input . Type . String ( ) , other . Input . Type . String ( ) ; expected != got {
return false
}
if expected , got := b . Input . StructFieldIndex , other . Input . StructFieldIndex ; ! reflect . DeepEqual ( expected , got ) {
return false
}
}
return true
}
2022-03-01 20:26:02 +01:00
// DependencyMatcher type alias describes a dependency match function.
type DependencyMatcher = func ( * Dependency , reflect . Type ) bool
// DefaultDependencyMatcher is the default dependency match function for all DI containers.
// It is used to collect dependencies from struct's fields and function's parameters.
var DefaultDependencyMatcher = func ( dep * Dependency , in reflect . Type ) bool {
2020-02-29 13:18:15 +01:00
if dep . Explicit {
return dep . DestType == in
}
return dep . DestType == nil || equalTypes ( dep . DestType , in )
}
2022-03-01 20:26:02 +01:00
// ToDependencyMatchFunc converts a DependencyMatcher (generic for all dependencies)
// to a dependency-specific input matcher.
func ToDependencyMatchFunc ( d * Dependency , match DependencyMatcher ) DependencyMatchFunc {
return func ( in reflect . Type ) bool {
return match ( d , in )
}
}
2021-04-22 13:00:00 +02:00
func getBindingsFor ( inputs [ ] reflect . Type , deps [ ] * Dependency , disablePayloadAutoBinding bool , paramsCount int ) ( bindings [ ] * binding ) {
2020-03-02 18:48:53 +01:00
// Path parameter start index is the result of [total path parameters] - [total func path parameters inputs],
// moving from last to first path parameters and first to last (probably) available input args.
//
// That way the above will work as expected:
// 1. mvc.New(app.Party("/path/{firstparam}")).Handle(....Controller.GetBy(secondparam string))
// 2. mvc.New(app.Party("/path/{firstparam}/{secondparam}")).Handle(...Controller.GetBy(firstparam, secondparam string))
2020-04-17 14:56:36 +02:00
// 3. usersRouter := app.Party("/users/{id:uint64}"); usersRouter.ConfigureContainer().Handle(method, "/", handler(id uint64))
// 4. usersRouter.Party("/friends").ConfigureContainer().Handle(method, "/{friendID:uint64}", handler(friendID uint64))
2020-03-02 18:48:53 +01:00
//
// Therefore, count the inputs that can be path parameters first.
shouldBindParams := make ( map [ int ] struct { } )
totalParamsExpected := 0
2020-03-05 18:49:45 +01:00
if paramsCount != - 1 {
for i , in := range inputs {
if _ , canBePathParameter := context . ParamResolvers [ in ] ; ! canBePathParameter {
continue
}
shouldBindParams [ i ] = struct { } { }
2020-02-29 13:18:15 +01:00
2020-03-05 18:49:45 +01:00
totalParamsExpected ++
}
2020-02-29 13:18:15 +01:00
}
2020-03-02 18:48:53 +01:00
startParamIndex := paramsCount - totalParamsExpected
if startParamIndex < 0 {
startParamIndex = 0
}
lastParamIndex := startParamIndex
2020-02-29 13:18:15 +01:00
2020-03-02 18:48:53 +01:00
getParamIndex := func ( ) int {
paramIndex := lastParamIndex
lastParamIndex ++
return paramIndex
}
bindedInput := make ( map [ int ] struct { } )
for i , in := range inputs { //order matters.
_ , canBePathParameter := shouldBindParams [ i ]
2020-02-29 13:18:15 +01:00
prevN := len ( bindings ) // to check if a new binding is attached; a dependency was matched (see below).
for j := len ( deps ) - 1 ; j >= 0 ; j -- {
d := deps [ j ]
// Note: we could use the same slice to return.
//
// Add all dynamic dependencies (caller-selecting) and the exact typed dependencies.
//
// A dependency can only be matched to 1 value, and 1 value has a single dependency
// (e.g. to avoid conflicting path parameters of the same type).
if _ , alreadyBinded := bindedInput [ j ] ; alreadyBinded {
continue
}
2022-03-01 20:26:02 +01:00
match := d . Match ( in )
2020-02-29 13:18:15 +01:00
if ! match {
continue
}
if canBePathParameter {
// wrap the existing dependency handler.
2020-03-02 18:48:53 +01:00
paramHandler := paramDependencyHandler ( getParamIndex ( ) )
2020-02-29 13:18:15 +01:00
prevHandler := d . Handle
2020-07-10 22:21:09 +02:00
d . Handle = func ( ctx * context . Context , input * Input ) ( reflect . Value , error ) {
2020-02-29 13:18:15 +01:00
v , err := paramHandler ( ctx , input )
if err != nil {
v , err = prevHandler ( ctx , input )
}
return v , err
}
d . Static = false
d . OriginalValue = nil
}
2020-03-01 07:34:53 +01:00
bindings = append ( bindings , & binding {
2020-02-29 13:18:15 +01:00
Dependency : d ,
Input : newInput ( in , i , nil ) ,
} )
if ! d . Explicit { // if explicit then it can be binded to more than one input
bindedInput [ j ] = struct { } { }
}
break
}
if prevN == len ( bindings ) {
2021-04-22 13:00:00 +02:00
if canBePathParameter { // Let's keep that option just for "payload": disablePayloadAutoBinding
2020-02-29 13:18:15 +01:00
// no new dependency added for this input,
// let's check for path parameters.
2020-03-02 18:48:53 +01:00
bindings = append ( bindings , paramBinding ( i , getParamIndex ( ) , in ) )
2020-02-29 13:18:15 +01:00
continue
}
2021-04-22 13:00:00 +02:00
// else, if payload binding is not disabled,
// add builtin request bindings that
// could be registered by end-dev but they didn't
if ! disablePayloadAutoBinding && isPayloadType ( in ) {
2020-02-29 13:18:15 +01:00
bindings = append ( bindings , payloadBinding ( i , in ) )
continue
}
}
}
return
}
2020-06-20 06:22:29 +02:00
func isPayloadType ( in reflect . Type ) bool {
switch indirectType ( in ) . Kind ( ) {
2020-06-20 15:06:26 +02:00
case reflect . Struct , reflect . Slice , reflect . Ptr :
2020-06-20 06:22:29 +02:00
return true
default :
return false
}
}
2021-04-22 13:00:00 +02:00
func getBindingsForFunc ( fn reflect . Value , dependencies [ ] * Dependency , disablePayloadAutoBinding bool , paramsCount int ) [ ] * binding {
2020-02-29 13:18:15 +01:00
fnTyp := fn . Type ( )
if ! isFunc ( fnTyp ) {
panic ( "bindings: unresolved: not a func type" )
}
n := fnTyp . NumIn ( )
inputs := make ( [ ] reflect . Type , n )
for i := 0 ; i < n ; i ++ {
inputs [ i ] = fnTyp . In ( i )
}
2021-04-22 13:00:00 +02:00
bindings := getBindingsFor ( inputs , dependencies , disablePayloadAutoBinding , paramsCount )
2020-10-31 04:04:05 +01:00
if expected , got := n , len ( bindings ) ; expected != got {
expectedInputs := ""
missingInputs := ""
for i , in := range inputs {
pos := i + 1
typName := in . String ( )
expectedInputs += fmt . Sprintf ( "\n - [%d] %s" , pos , typName )
found := false
for _ , b := range bindings {
if b . Input . Index == i {
found = true
break
}
}
if ! found {
missingInputs += fmt . Sprintf ( "\n - [%d] %s" , pos , typName )
}
}
fnName := context . HandlerName ( fn )
panic ( fmt . Sprintf ( "expected [%d] bindings (input parameters) but got [%d]\nFunction:\n - %s\nExpected:%s\nMissing:%s" ,
expected , got , fnName , expectedInputs , missingInputs ) )
2020-02-29 13:18:15 +01:00
}
return bindings
}
2022-03-01 20:26:02 +01:00
func getBindingsForStruct ( v reflect . Value , dependencies [ ] * Dependency , markExportedFieldsAsRequired bool , disablePayloadAutoBinding bool , matchDependency DependencyMatcher , paramsCount int , sorter Sorter ) ( bindings [ ] * binding ) {
2020-02-29 13:18:15 +01:00
typ := indirectType ( v . Type ( ) )
if typ . Kind ( ) != reflect . Struct {
panic ( "bindings: unresolved: no struct type" )
}
// get bindings from any struct's non zero values first, including unexported.
elem := reflect . Indirect ( v )
nonZero := lookupNonZeroFieldValues ( elem )
for _ , f := range nonZero {
// fmt.Printf("Controller [%s] | NonZero | Field Index: %v | Field Type: %s\n", typ, f.Index, f.Type)
2020-03-01 07:34:53 +01:00
bindings = append ( bindings , & binding {
2022-03-01 20:26:02 +01:00
Dependency : newDependency ( elem . FieldByIndex ( f . Index ) . Interface ( ) , disablePayloadAutoBinding , nil ) ,
2020-08-24 20:44:29 +02:00
Input : newStructFieldInput ( f ) ,
2020-02-29 13:18:15 +01:00
} )
}
2020-07-17 11:59:39 +02:00
fields , stateless := lookupFields ( elem , true , true , nil )
2020-02-29 13:18:15 +01:00
n := len ( fields )
if n > 1 && sorter != nil {
sort . Slice ( fields , func ( i , j int ) bool {
return sorter ( fields [ i ] . Type , fields [ j ] . Type )
} )
}
inputs := make ( [ ] reflect . Type , n )
for i := 0 ; i < n ; i ++ {
2020-07-17 11:59:39 +02:00
// fmt.Printf("Controller [%s] | Field Index: %v | Field Type: %s\n", typ, fields[i].Index, fields[i].Type)
2020-02-29 13:18:15 +01:00
inputs [ i ] = fields [ i ] . Type
}
2020-07-24 15:50:00 +02:00
2021-04-22 13:00:00 +02:00
exportedBindings := getBindingsFor ( inputs , dependencies , disablePayloadAutoBinding , paramsCount )
2020-07-24 20:09:30 +02:00
// fmt.Printf("Controller [%s] | Inputs length: %d vs Bindings length: %d | NonZero: %d | Stateless : %d\n",
// typ, n, len(exportedBindings), len(nonZero), stateless)
2020-07-24 15:50:00 +02:00
// for i, b := range exportedBindings {
2020-07-24 20:09:30 +02:00
// fmt.Printf("[%d] [Static=%v] %#+v\n", i, b.Dependency.Static, b.Dependency.OriginalValue)
2020-07-24 15:50:00 +02:00
// }
2020-02-29 13:18:15 +01:00
2021-04-22 13:00:00 +02:00
if markExportedFieldsAsRequired && len ( exportedBindings ) != n {
panic ( fmt . Sprintf ( "MarkExportedFieldsAsRequired is true and at least one of struct's (%s) field was not binded to a dependency.\nFields length: %d, matched exported bindings length: %d.\nUse the Reporter for further details" , typ . String ( ) , n , len ( exportedBindings ) ) )
}
2020-07-17 11:59:39 +02:00
if stateless == 0 && len ( nonZero ) >= len ( exportedBindings ) {
// if we have not a single stateless and fields are defined then just return.
// Note(@kataras): this can accept further improvements.
2020-02-29 13:18:15 +01:00
return
}
// get declared bindings from deps.
bindings = append ( bindings , exportedBindings ... )
for _ , binding := range bindings {
2020-07-17 11:59:39 +02:00
// fmt.Printf(""Controller [%s] | Binding: %s\n", typ, binding.String())
2020-02-29 13:18:15 +01:00
if len ( binding . Input . StructFieldIndex ) == 0 {
2020-08-24 20:44:29 +02:00
// set correctly the input's field index and name.
f := fields [ binding . Input . Index ]
binding . Input . StructFieldIndex = f . Index
binding . Input . StructFieldName = f . Name
2020-02-29 13:18:15 +01:00
}
2020-03-01 07:34:53 +01:00
// fmt.Printf("Controller [%s] | binding Index: %v | binding Type: %s\n", typ, binding.Input.StructFieldIndex, binding.Input.Type)
2020-07-17 11:59:39 +02:00
// fmt.Printf("Controller [%s] Set [%s] to struct field index: %v\n", typ.String(), binding.Input.Type.String(), binding.Input.StructFieldIndex)
2020-02-29 13:18:15 +01:00
}
return
}
2021-06-10 20:16:00 +02:00
func getStaticInputs ( bindings [ ] * binding , numIn int ) [ ] reflect . Value {
inputs := make ( [ ] reflect . Value , numIn )
for _ , b := range bindings {
if d := b . Dependency ; d != nil && d . Static {
inputs [ b . Input . Index ] , _ = d . Handle ( nil , nil )
}
}
return inputs
}
2020-02-29 13:18:15 +01:00
/ *
Builtin dynamic bindings .
* /
2020-03-01 07:34:53 +01:00
func paramBinding ( index , paramIndex int , typ reflect . Type ) * binding {
return & binding {
2020-02-29 13:18:15 +01:00
Dependency : & Dependency { Handle : paramDependencyHandler ( paramIndex ) , DestType : typ , Source : getSource ( ) } ,
Input : newInput ( typ , index , nil ) ,
}
}
func paramDependencyHandler ( paramIndex int ) DependencyHandler {
2020-07-10 22:21:09 +02:00
return func ( ctx * context . Context , input * Input ) ( reflect . Value , error ) {
2020-02-29 13:18:15 +01:00
if ctx . Params ( ) . Len ( ) <= paramIndex {
return emptyValue , ErrSeeOther
}
return reflect . ValueOf ( ctx . Params ( ) . Store [ paramIndex ] . ValueRaw ) , nil
}
}
// registered if input parameters are more than matched dependencies.
2020-07-24 20:09:30 +02:00
// It binds an input to a request body based on the request content-type header
// (JSON, Protobuf, Msgpack, XML, YAML, Query, Form).
2020-03-01 07:34:53 +01:00
func payloadBinding ( index int , typ reflect . Type ) * binding {
2020-07-24 20:09:30 +02:00
// fmt.Printf("Register payload binding for index: %d and type: %s\n", index, typ.String())
2020-03-01 07:34:53 +01:00
return & binding {
2020-02-29 13:18:15 +01:00
Dependency : & Dependency {
2020-07-10 22:21:09 +02:00
Handle : func ( ctx * context . Context , input * Input ) ( newValue reflect . Value , err error ) {
2020-02-29 13:18:15 +01:00
wasPtr := input . Type . Kind ( ) == reflect . Ptr
2020-05-19 08:28:27 +02:00
if serveDepsV := ctx . Values ( ) . Get ( context . DependenciesContextKey ) ; serveDepsV != nil {
if serveDeps , ok := serveDepsV . ( context . DependenciesMap ) ; ok {
if newValue , ok = serveDeps [ typ ] ; ok {
return
}
}
}
2020-06-20 06:22:29 +02:00
if input . Type . Kind ( ) == reflect . Slice {
newValue = reflect . New ( reflect . SliceOf ( indirectType ( input . Type ) ) )
} else {
newValue = reflect . New ( indirectType ( input . Type ) )
}
2020-02-29 13:18:15 +01:00
ptr := newValue . Interface ( )
2020-04-08 16:27:23 +02:00
err = ctx . ReadBody ( ptr )
2020-06-20 06:22:29 +02:00
2020-02-29 13:18:15 +01:00
if ! wasPtr {
newValue = newValue . Elem ( )
}
return
} ,
Source : getSource ( ) ,
} ,
Input : newInput ( typ , index , nil ) ,
}
}