diff --git a/README.md b/README.md
index 0a17f60e..a6903120 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# Iris
+# Iris
[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=for-the-badge)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=for-the-badge)](https://goreportcard.com/report/github.com/kataras/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=for-the-badge)](https://github.com/kataras/iris/tree/master/_examples) [![chat](https://img.shields.io/gitter/room/iris_go/community.svg?color=blue&logo=gitter&style=for-the-badge)](https://gitter.im/iris_go/community) [![release](https://img.shields.io/badge/release%20-v11.2-0077b3.svg?style=for-the-badge)](https://github.com/kataras/iris/releases)
diff --git a/README_GR.md b/README_GR.md
index 01e75baa..672ce0ac 100644
--- a/README_GR.md
+++ b/README_GR.md
@@ -1,4 +1,4 @@
-# Iris
+# Iris
[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=for-the-badge)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=for-the-badge)](https://goreportcard.com/report/github.com/kataras/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=for-the-badge)](https://github.com/kataras/iris/tree/master/_examples) [![chat](https://img.shields.io/gitter/room/iris_go/community.svg?color=blue&logo=gitter&style=for-the-badge)](https://gitter.im/iris_go/community) [![release](https://img.shields.io/badge/release%20-v11.2-0077b3.svg?style=for-the-badge)](https://github.com/kataras/iris/releases)
diff --git a/README_ZH.md b/README_ZH.md
index 86607db8..1217e70c 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -1,6 +1,6 @@
-# Iris
+# Iris
[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=for-the-badge)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=for-the-badge)](https://goreportcard.com/report/github.com/kataras/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=for-the-badge)](https://github.com/kataras/iris/tree/master/_examples) [![chat](https://img.shields.io/gitter/room/iris_go/community.svg?color=blue&logo=gitter&style=for-the-badge)](https://gitter.im/iris_go/community) [![release](https://img.shields.io/badge/release%20-v11.2-0077b3.svg?style=for-the-badge)](https://github.com/kataras/iris/releases)
diff --git a/hero/di/di.go b/hero/di/di.go
index 3b8b9b7b..83eb80aa 100644
--- a/hero/di/di.go
+++ b/hero/di/di.go
@@ -33,6 +33,7 @@ func Struct(s interface{}, values ...reflect.Value) *StructInjector {
ValueOf(s),
DefaultHijacker,
DefaultTypeChecker,
+ SortByNumMethods,
Values(values).CloneWithFieldsOf(s)...,
)
}
@@ -64,6 +65,7 @@ type D struct {
hijacker Hijacker
goodFunc TypeChecker
+ sorter Sorter
}
// New creates and returns a new Dependency Injection container.
@@ -85,13 +87,20 @@ func (d *D) GoodFunc(fn TypeChecker) *D {
return d
}
+// Sort sets the fields and valid bindable values sorter for struct injection.
+func (d *D) Sort(with Sorter) *D {
+ d.sorter = with
+ return d
+}
+
// Clone returns a new Dependency Injection container, it adopts the
-// parent's (current "D") hijacker, good func type checker and all dependencies values.
+// 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,
}
}
@@ -108,6 +117,7 @@ func (d *D) Struct(s interface{}) *StructInjector {
ValueOf(s),
d.hijacker,
d.goodFunc,
+ d.sorter,
d.Values.CloneWithFieldsOf(s)...,
)
}
diff --git a/hero/di/reflect.go b/hero/di/reflect.go
index 48fbc708..730d91ab 100644
--- a/hero/di/reflect.go
+++ b/hero/di/reflect.go
@@ -178,11 +178,6 @@ type field struct {
Name string // the actual name.
Index []int // the index of the field, slice if it's part of a embedded struct
CanSet bool // is true if it's exported.
-
- // this could be empty, but in our cases it's not,
- // it's filled with the bind object (as service which means as static value)
- // and it's filled from the lookupFields' caller.
- AnyValue reflect.Value
}
// NumFields returns the total number of fields, and the embedded, even if the embedded struct is not exported,
diff --git a/hero/di/struct.go b/hero/di/struct.go
index 53384c01..56fa0b68 100644
--- a/hero/di/struct.go
+++ b/hero/di/struct.go
@@ -3,6 +3,7 @@ package di
import (
"fmt"
"reflect"
+ "sort"
)
// Scope is the struct injector's struct value scope/permant state.
@@ -40,6 +41,8 @@ type (
targetStructField struct {
Object *BindObject
FieldIndex []int
+ // ValueIndex is used mostly for debugging, it's the order of the registered binded value targets to that field.
+ ValueIndex int
}
// StructInjector keeps the data that are needed in order to do the binding injection
@@ -68,13 +71,37 @@ func (s *StructInjector) countBindType(typ BindType) (n int) {
return
}
+// 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 package-level struct injector function `Struct`.
+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
+}
+
// MakeStructInjector returns a new struct injector, which will be the object
// that the caller should use to bind exported fields or
// embedded unexported fields that contain exported fields
// of the "v" struct value or pointer.
//
// The hijack and the goodFunc are optional, the "values" is the dependencies collection.
-func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *StructInjector {
+func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker, sorter Sorter, values ...reflect.Value) *StructInjector {
s := &StructInjector{
initRef: v,
initRefAsSlice: []reflect.Value{v},
@@ -96,7 +123,19 @@ func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker,
visited := make(map[int]struct{}, 0) // add a visited to not add twice a single value (09-Jul-2019).
fields := lookupFields(s.elemType, true, nil)
+
+ // for idx, val := range values {
+ // fmt.Printf("[%d] value type [%s] value name [%s]\n", idx, val.Type().String(), val.String())
+ // }
+
+ if len(fields) > 1 && sorter != nil {
+ sort.Slice(fields, func(i, j int) bool {
+ return sorter(fields[i].Type, fields[j].Type)
+ })
+ }
+
for _, f := range fields {
+ // fmt.Printf("[%d] field type [%s] value name [%s]\n", idx, f.Type.String(), f.Name)
if hijack != nil {
if b, ok := hijack(f.Type); ok && b != nil {
s.fields = append(s.fields, &targetStructField{
@@ -108,6 +147,8 @@ func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker,
}
}
+ var possibleValues []*targetStructField
+
for idx, val := range values {
if _, alreadySet := visited[idx]; alreadySet {
continue
@@ -120,15 +161,33 @@ func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker,
}
if b.IsAssignable(f.Type) {
- visited[idx] = struct{}{}
- // fmt.Printf("bind the object to the field: %s at index: %#v and type: %s\n", f.Name, f.Index, f.Type.String())
- s.fields = append(s.fields, &targetStructField{
+ possibleValues = append(possibleValues, &targetStructField{
+ ValueIndex: idx,
FieldIndex: f.Index,
Object: &b,
})
- break
}
}
+
+ if l := len(possibleValues); l == 0 {
+ continue
+ } else if l > 1 && sorter != nil {
+ sort.Slice(possibleValues, func(i, j int) bool {
+ // if first.Object.BindType != second.Object.BindType {
+ // return true
+ // }
+
+ // if first.Object.BindType != Static { // dynamic goes last.
+ // return false
+ // }
+ return sorter(possibleValues[i].Object.Type, possibleValues[j].Object.Type)
+ })
+ }
+
+ tf := possibleValues[0]
+ visited[tf.ValueIndex] = struct{}{}
+ // fmt.Printf("bind the object to the field: %s at index: %#v and type: %s\n", f.Name, f.Index, f.Type.String())
+ s.fields = append(s.fields, tf)
}
s.Has = len(s.fields) > 0
diff --git a/httptest/status.go b/httptest/status.go
index 2c2c76d4..af7cab46 100644
--- a/httptest/status.go
+++ b/httptest/status.go
@@ -2,14 +2,12 @@ package httptest
// HTTP status codes as registered with IANA.
// See: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
-// Raw Copy from the net/http std package in order to recude the import path of "net/http" for the users.
-//
-// These may or may not stay.
+// Raw Copy from the future(tip) net/http std package in order to recude the import path of "net/http" for the users.
const (
- StatusContinue = 100 // RFC 7231, 6.2.1
- StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
- StatusProcessing = 102 // RFC 2518, 10.1
-
+ StatusContinue = 100 // RFC 7231, 6.2.1
+ StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
+ StatusProcessing = 102 // RFC 2518, 10.1
+ StatusEarlyHints = 103 // RFC 8297
StatusOK = 200 // RFC 7231, 6.3.1
StatusCreated = 201 // RFC 7231, 6.3.2
StatusAccepted = 202 // RFC 7231, 6.3.3
diff --git a/iris.go b/iris.go
index c854e533..05e6ffec 100644
--- a/iris.go
+++ b/iris.go
@@ -39,15 +39,13 @@ import (
const Version = "11.2.8"
// HTTP status codes as registered with IANA.
-// See: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
-// Raw Copy from the net/http std package in order to recude the import path of "net/http" for the users.
-//
-// Copied from `net/http` package.
+// See: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml.
+// Raw Copy from the future(tip) net/http std package in order to recude the import path of "net/http" for the users.
const (
- StatusContinue = 100 // RFC 7231, 6.2.1
- StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
- StatusProcessing = 102 // RFC 2518, 10.1
-
+ StatusContinue = 100 // RFC 7231, 6.2.1
+ StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
+ StatusProcessing = 102 // RFC 2518, 10.1
+ StatusEarlyHints = 103 // RFC 8297
StatusOK = 200 // RFC 7231, 6.3.1
StatusCreated = 201 // RFC 7231, 6.3.2
StatusAccepted = 202 // RFC 7231, 6.3.3
diff --git a/mvc/controller.go b/mvc/controller.go
index 8c10d49b..a6c3ddfd 100644
--- a/mvc/controller.go
+++ b/mvc/controller.go
@@ -86,6 +86,7 @@ type ControllerActivator struct {
// Can be bind-ed to the the new controller's fields and method that is fired
// on incoming requests.
dependencies di.Values
+ sorter di.Sorter
errorHandler hero.ErrorHandler
@@ -108,7 +109,7 @@ func NameOf(v interface{}) string {
return fullname
}
-func newControllerActivator(router router.Party, controller interface{}, dependencies []reflect.Value, errorHandler hero.ErrorHandler) *ControllerActivator {
+func newControllerActivator(router router.Party, controller interface{}, dependencies []reflect.Value, sorter di.Sorter, errorHandler hero.ErrorHandler) *ControllerActivator {
typ := reflect.TypeOf(controller)
c := &ControllerActivator{
@@ -128,6 +129,7 @@ func newControllerActivator(router router.Party, controller interface{}, depende
routes: whatReservedMethods(typ),
// CloneWithFieldsOf: include the manual fill-ed controller struct's fields to the dependencies.
dependencies: di.Values(dependencies).CloneWithFieldsOf(controller),
+ sorter: sorter,
errorHandler: errorHandler,
}
@@ -386,7 +388,14 @@ var emptyIn = []reflect.Value{}
func (c *ControllerActivator) attachInjector() {
if c.injector == nil {
- c.injector = di.Struct(c.Value, c.dependencies...)
+ c.injector = di.MakeStructInjector(
+ di.ValueOf(c.Value),
+ di.DefaultHijacker,
+ di.DefaultTypeChecker,
+ c.sorter,
+ di.Values(c.dependencies).CloneWithFieldsOf(c.Value)...,
+ )
+ // c.injector = di.Struct(c.Value, c.dependencies...)
if !c.servesWebsocket {
golog.Debugf("MVC Controller [%s] [Scope=%s]", c.fullName, c.injector.Scope)
} else {
diff --git a/mvc/mvc.go b/mvc/mvc.go
index a58c2859..4ceb7d89 100644
--- a/mvc/mvc.go
+++ b/mvc/mvc.go
@@ -39,7 +39,12 @@ var HeroDependencies = true
//
// See `mvc#New` for more.
type Application struct {
- Dependencies di.Values
+ Dependencies di.Values
+ // Sorter is a `di.Sorter`, can be used to customize the order of controller's fields
+ // and their available bindable values to set.
+ // Sorting matters only when a field can accept more than one registered value.
+ // Defaults to nil; order of registration matters when more than one field can accept the same value.
+ Sorter di.Sorter
Router router.Party
Controllers []*ControllerActivator
websocketControllers []websocket.ConnHandler
@@ -129,6 +134,15 @@ Set the Logger's Level to "debug" to view the active dependencies per controller
return app
}
+// SortByNumMethods is the same as `app.Sorter = di.SortByNumMethods` which
+// prioritize fields and their available values (only if more than one)
+// with the highest number of methods,
+// this way an empty interface{} is getting the "thinnest" available value.
+func (app *Application) SortByNumMethods() *Application {
+ app.Sorter = di.SortByNumMethods
+ return app
+}
+
// Handle serves a controller for the current mvc application's Router.
// It accept any custom struct which its functions will be transformed
// to routes.
@@ -218,7 +232,7 @@ func (app *Application) GetNamespaces() websocket.Namespaces {
func (app *Application) handle(controller interface{}) *ControllerActivator {
// initialize the controller's activator, nothing too magical so far.
- c := newControllerActivator(app.Router, controller, app.Dependencies, app.ErrorHandler)
+ c := newControllerActivator(app.Router, controller, app.Dependencies, app.Sorter, app.ErrorHandler)
// check the controller's "BeforeActivation" or/and "AfterActivation" method(s) between the `activate`
// call, which is simply parses the controller's methods, end-dev can register custom controller's methods