Skip to content

Commit

Permalink
Add status code 103 Early Hints. Add the ability to customize and cha…
Browse files Browse the repository at this point in the history
…nge the order of controller's fields and their registered valid dependencies relative to: #1343
  • Loading branch information
kataras committed Aug 26, 2019
1 parent bcd6c63 commit 9a74571
Show file tree
Hide file tree
Showing 10 changed files with 116 additions and 33 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- # Iris Web Framework <a href="README_ZH.md"> <img width="20px" src="https://iris-go.com/images/flag-china.svg?v=10" /></a> <a href="README_RU.md"><img width="20px" src="https://iris-go.com/images/flag-russia.svg?v=10" /></a> <a href="README_ID.md"> <img width="20px" src="https://iris-go.com/images/flag-indonesia.svg?v=10" /></a> <a href="README_GR.md"><img width="20px" src="https://iris-go.com/images/flag-greece.svg?v=10" /></a> <a href="README_PT_BR.md"><img width="20px" src="https://iris-go.com/images/flag-pt-br.svg?v=10" /></a> <a href="README_JPN.md"><img width="20px" src="https://iris-go.com/images/flag-japan.svg?v=10" /></a> -->

# Iris <a href="README_ZH.md"><img width="20px" src="https://iris-go.com/images/flag-china.svg?v=10" /></a> <a href="README_GR.md"><img width="20px" src="https://iris-go.com/images/flag-greece.svg?v=10" /></a> <a href="README_ES.md"><img width="20px" src="https://www.shareicon.net/data/48x48/2016/09/01/822583_world_512x512.png" /></a>
# Iris <a href="README_ZH.md"><img width="20px" src="https://iris-go.com/images/flag-china.svg?v=10" /></a> <a href="README_GR.md"><img width="20px" src="https://iris-go.com/images/flag-greece.svg?v=10" /></a> <a href="README_ES.md"><img width="20px" src="https://iris-go.com/images/flag-spain.png" /></a>

[![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)<!--[![godocs](https://img.shields.io/badge/go-%20docs-488AC7.svg?style=for-the-badge)](https://godoc.org/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)

Expand Down
2 changes: 1 addition & 1 deletion README_GR.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Iris <a href="README.md"> <img width="20px" src="https://iris-go.com/images/flag-unitedkingdom.svg?v=10" /></a> <a href="README_ZH.md"><img width="20px" src="https://iris-go.com/images/flag-china.svg?v=10" /></a> <a href="README_ES.md"><img width="20px" src="https://www.shareicon.net/data/48x48/2016/09/01/822583_world_512x512.png" /></a>
# Iris <a href="README.md"> <img width="20px" src="https://iris-go.com/images/flag-unitedkingdom.svg?v=10" /></a> <a href="README_ZH.md"><img width="20px" src="https://iris-go.com/images/flag-china.svg?v=10" /></a> <a href="README_ES.md"><img width="20px" src="https://iris-go.com/images/flag-spain.png" /></a>

[![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)<!--[![godocs](https://img.shields.io/badge/go-%20docs-488AC7.svg?style=for-the-badge)](https://godoc.org/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)

Expand Down
2 changes: 1 addition & 1 deletion README_ZH.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- # Iris Web Framework <a href="README_ZH.md"> <img width="20px" src="https://iris-go.com/images/flag-china.svg?v=10" /></a> <a href="README_RU.md"><img width="20px" src="https://iris-go.com/images/flag-russia.svg?v=10" /></a> <a href="README_ID.md"> <img width="20px" src="https://iris-go.com/images/flag-indonesia.svg?v=10" /></a> <a href="README_GR.md"><img width="20px" src="https://iris-go.com/images/flag-greece.svg?v=10" /></a> <a href="README_PT_BR.md"><img width="20px" src="https://iris-go.com/images/flag-pt-br.svg?v=10" /></a> <a href="README_JPN.md"><img width="20px" src="https://iris-go.com/images/flag-japan.svg?v=10" /></a> -->

# Iris <a href="README.md"> <img width="20px" src="https://iris-go.com/images/flag-unitedkingdom.svg?v=10" /></a> <a href="README_GR.md"><img width="20px" src="https://iris-go.com/images/flag-greece.svg?v=10" /></a> <a href="README_ES.md"><img width="20px" src="https://www.shareicon.net/data/48x48/2016/09/01/822583_world_512x512.png" /></a>
# Iris <a href="README.md"> <img width="20px" src="https://iris-go.com/images/flag-unitedkingdom.svg?v=10" /></a> <a href="README_GR.md"><img width="20px" src="https://iris-go.com/images/flag-greece.svg?v=10" /></a> <a href="README_ES.md"><img width="20px" src="https://iris-go.com/images/flag-spain.png" /></a>

[![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)<!--[![godocs](https://img.shields.io/badge/go-%20docs-488AC7.svg?style=for-the-badge)](https://godoc.org/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)

Expand Down
12 changes: 11 additions & 1 deletion hero/di/di.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func Struct(s interface{}, values ...reflect.Value) *StructInjector {
ValueOf(s),
DefaultHijacker,
DefaultTypeChecker,
SortByNumMethods,
Values(values).CloneWithFieldsOf(s)...,
)
}
Expand Down Expand Up @@ -64,6 +65,7 @@ type D struct {

hijacker Hijacker
goodFunc TypeChecker
sorter Sorter
}

// New creates and returns a new Dependency Injection container.
Expand All @@ -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,
}
}

Expand All @@ -108,6 +117,7 @@ func (d *D) Struct(s interface{}) *StructInjector {
ValueOf(s),
d.hijacker,
d.goodFunc,
d.sorter,
d.Values.CloneWithFieldsOf(s)...,
)
}
Expand Down
5 changes: 0 additions & 5 deletions hero/di/reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
69 changes: 64 additions & 5 deletions hero/di/struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package di
import (
"fmt"
"reflect"
"sort"
)

// Scope is the struct injector's struct value scope/permant state.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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},
Expand All @@ -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{
Expand All @@ -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
Expand All @@ -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
Expand Down
12 changes: 5 additions & 7 deletions httptest/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 6 additions & 8 deletions iris.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 11 additions & 2 deletions mvc/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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{
Expand All @@ -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,
}

Expand Down Expand Up @@ -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 {
Expand Down
18 changes: 16 additions & 2 deletions mvc/mvc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 9a74571

Please sign in to comment.