From bd5f96086ba6d5685f0028ba677c11e803ad5f4b Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Mon, 26 Aug 2019 16:43:02 +0300 Subject: [PATCH] Add status code 103 Early Hints. Add the ability to customize and change the order of controller's fields and their registered valid dependencies relative to: #1343 Former-commit-id: 5bd9e02e5fafca135d17cad87f4a9aec526b333b --- README.md | 2 +- README_GR.md | 2 +- README_ZH.md | 2 +- hero/di/di.go | 12 +++++++- hero/di/reflect.go | 5 ---- hero/di/struct.go | 69 ++++++++++++++++++++++++++++++++++++++++++---- httptest/status.go | 12 ++++---- iris.go | 14 ++++------ mvc/controller.go | 13 +++++++-- mvc/mvc.go | 18 ++++++++++-- 10 files changed, 116 insertions(+), 33 deletions(-) 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