diff --git a/HISTORY.md b/HISTORY.md index eb6addcd2..39b493959 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -21,6 +21,20 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene **How to upgrade**: Open your command-line and execute this command: `go get github.com/kataras/iris/v12@latest`. +# Su, 16 February 2020 | v12.1.8 + +New Features: + +- [[FEATURE REQUEST] MVC serving gRPC-compatible controller](https://github.com/kataras/iris/issues/1449) + +Fixes: + +- [App can't find embedded pug template files by go-bindata](https://github.com/kataras/iris/issues/1450) + +New Examples: + +- [_examples/mvc/grpc-compatible](_examples/mvc/grpc-compatible) + # Mo, 10 February 2020 | v12.1.7 Implement **new** `SetRegisterRule(iris.RouteOverride, RouteSkip, RouteError)` to resolve: https://github.com/kataras/iris/issues/1448 diff --git a/HISTORY_ES.md b/HISTORY_ES.md index 9cf22c7ed..ccdaa84a3 100644 --- a/HISTORY_ES.md +++ b/HISTORY_ES.md @@ -21,9 +21,9 @@ Los desarrolladores no están obligados a actualizar si realmente no lo necesita **Cómo actualizar**: Abra su línea de comandos y ejecute este comando: `go get github.com/kataras/iris/v12@latest`. -# Mo, 10 February 2020 | v12.1.7 +# Su, 16 February 2020 | v12.1.8 -Not translated yet, please navigate to the [english version](HISTORY.md#mo-10-february-2020--v1217) instead. +Not translated yet, please navigate to the [english version](HISTORY.md#su-16-february-2020--v1218) instead. # Sábado, 26 de octubre 2019 | v12.0.0 diff --git a/README.md b/README.md index 108acb225..8e381fb66 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ - +![](https://iris-go.com/images/sponsor.png) Support your favorite web framework through [Github Sponsors Program](https://github.com/sponsors/kataras)! + # Iris Web Framework [![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=for-the-badge&logo=travis)](https://travis-ci.org/kataras/iris) [![FOSSA Status](https://img.shields.io/badge/LICENSE%20SCAN-PASSING❤️-CD2956?style=for-the-badge&logo=fossa)](https://app.fossa.io/projects/git%2Bgithub.com%2Fkataras%2Firis?ref=badge_shield) [![view examples](https://img.shields.io/badge/learn%20by-examples-0C8EC5.svg?style=for-the-badge&logo=go)](https://github.com/kataras/iris/tree/master/_examples) [![chat](https://img.shields.io/gitter/room/iris_go/community.svg?color=7E18DD&logo=gitter&style=for-the-badge)](https://gitter.im/iris_go/community) diff --git a/VERSION b/VERSION index 9b41a4e0e..b56d45d59 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -12.1.7:https://github.com/kataras/iris/releases/tag/v12.1.7 \ No newline at end of file +12.1.8:https://github.com/kataras/iris/releases/tag/v12.1.8 \ No newline at end of file diff --git a/_examples/docker/go.mod b/_examples/docker/go.mod index bc58a1195..1568e55a2 100644 --- a/_examples/docker/go.mod +++ b/_examples/docker/go.mod @@ -3,6 +3,6 @@ module app go 1.13 require ( - github.com/kataras/iris/v12 v12.1.7 + github.com/kataras/iris/v12 v12.1.8 github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect ) diff --git a/_examples/mvc/grpc-compatible/main.go b/_examples/mvc/grpc-compatible/main.go index 8d775b814..93298b035 100644 --- a/_examples/mvc/grpc-compatible/main.go +++ b/_examples/mvc/grpc-compatible/main.go @@ -31,11 +31,19 @@ func newApp() *iris.Application { return ctx.Request().Context() }). // Bind loginRequest. - Register(func(ctx iris.Context) loginRequest { - var req loginRequest - ctx.ReadJSON(&req) - return req - }). + // Register(func(ctx iris.Context) loginRequest { + // var req loginRequest + // ctx.ReadJSON(&req) + // return req + // }). + // OR + // Bind any other structure or pointer to a structure from request's + // XML + // YAML + // Query + // Form + // JSON (default, if not client's "Content-Type" specified otherwise) + Register(mvc.AutoBinding). Handle(&myController{}) return app diff --git a/_examples/mvc/grpc-compatible/main_test.go b/_examples/mvc/grpc-compatible/main_test.go index cfe369820..2211b5b76 100644 --- a/_examples/mvc/grpc-compatible/main_test.go +++ b/_examples/mvc/grpc-compatible/main_test.go @@ -6,7 +6,7 @@ import ( "github.com/kataras/iris/v12/httptest" ) -func TestBindContextContext(t *testing.T) { +func TestGRPCCompatible(t *testing.T) { app := newApp() e := httptest.New(t, app) diff --git a/_examples/view/template_pug_3/bindata.go b/_examples/view/template_pug_3/bindata.go index aaac24f6d..fb9d699af 100644 --- a/_examples/view/template_pug_3/bindata.go +++ b/_examples/view/template_pug_3/bindata.go @@ -215,9 +215,9 @@ type bintree struct { } var _bintree = &bintree{nil, map[string]*bintree{ - "templates": &bintree{nil, map[string]*bintree{ - "index.pug": &bintree{templatesIndexPug, map[string]*bintree{}}, - "layout.pug": &bintree{templatesLayoutPug, map[string]*bintree{}}, + "templates": {nil, map[string]*bintree{ + "index.pug": {templatesIndexPug, map[string]*bintree{}}, + "layout.pug": {templatesLayoutPug, map[string]*bintree{}}, }}, }} diff --git a/doc.go b/doc.go index 3e3ce9399..bf97c9379 100644 --- a/doc.go +++ b/doc.go @@ -38,7 +38,7 @@ Source code and other details for the project are available at GitHub: Current Version -12.1.7 +12.1.8 Installation diff --git a/hero/di.go b/hero/di.go deleted file mode 100644 index 35c8158bd..000000000 --- a/hero/di.go +++ /dev/null @@ -1,43 +0,0 @@ -package hero - -import ( - "reflect" - - "github.com/kataras/iris/v12/context" - "github.com/kataras/iris/v12/hero/di" -) - -func init() { - di.DefaultHijacker = func(fieldOrFuncInput reflect.Type) (*di.BindObject, bool) { - if !IsContext(fieldOrFuncInput) { - return nil, false - } - // this is being used on both func injector and struct injector. - // if the func's input argument or the struct's field is a type of Context - // then we can do a fast binding using the ctxValue - // which is used as slice of reflect.Value, because of the final method's `Call`. - return &di.BindObject{ - Type: contextTyp, - BindType: di.Dynamic, - ReturnValue: func(ctx context.Context) reflect.Value { - return ctx.ReflectValue()[0] - }, - }, true - } - - di.DefaultTypeChecker = func(fn reflect.Type) bool { - // valid if that single input arg is a typeof context.Context - // or first argument is context.Context and second argument is a variadic, which is ignored (i.e new sessions#Start). - return (fn.NumIn() == 1 || (fn.NumIn() == 2 && fn.IsVariadic())) && IsContext(fn.In(0)) - } - - di.DefaultErrorHandler = di.ErrorHandlerFunc(func(ctx context.Context, err error) { - if err == nil { - return - } - - ctx.StatusCode(400) - ctx.WriteString(err.Error()) - ctx.StopExecution() - }) -} diff --git a/hero/di/di.go b/hero/di/di.go index 9f23ca469..925f221bf 100644 --- a/hero/di/di.go +++ b/hero/di/di.go @@ -9,12 +9,6 @@ import ( ) type ( - // Hijacker is a type which is used to catch fields or function's input argument - // to bind a custom object based on their type. - Hijacker func(reflect.Type) (*BindObject, bool) - // TypeChecker checks if a specific field's or function input argument's - // is valid to be binded. - TypeChecker func(reflect.Type) bool // ErrorHandler is the optional interface to handle errors per hero func, // see `mvc/Application#HandleError` for MVC application-level error handler registration too. // @@ -34,15 +28,51 @@ func (fn ErrorHandlerFunc) HandleError(ctx context.Context, err error) { fn(ctx, err) } -var ( - // DefaultHijacker is the hijacker used on the package-level Struct & Func functions. - DefaultHijacker Hijacker - // DefaultTypeChecker is the typechecker used on the package-level Struct & Func functions. - DefaultTypeChecker TypeChecker - // DefaultErrorHandler is the error handler used on the package-level `Func` function - // to catch any errors from dependencies or handlers. - DefaultErrorHandler ErrorHandler -) +// DefaultErrorHandler is the default error handler will be fired on +// any error from registering a request-scoped dynamic dependency and on a controller's method failure. +var DefaultErrorHandler ErrorHandler = ErrorHandlerFunc(func(ctx context.Context, err error) { + if err == nil { + return + } + + ctx.StatusCode(400) + ctx.WriteString(err.Error()) + ctx.StopExecution() +}) + +var emptyValue reflect.Value + +// DefaultFallbackBinder used to bind any oprhan inputs. Its error is handled by the `ErrorHandler`. +var DefaultFallbackBinder FallbackBinder = func(ctx context.Context, input OrphanInput) (newValue reflect.Value, err error) { + wasPtr := input.Type.Kind() == reflect.Ptr + + newValue = reflect.New(IndirectType(input.Type)) + ptr := newValue.Interface() + + switch ctx.GetContentTypeRequested() { + case context.ContentXMLHeaderValue: + err = ctx.ReadXML(ptr) + case context.ContentYAMLHeaderValue: + err = ctx.ReadYAML(ptr) + case context.ContentFormHeaderValue: + err = ctx.ReadQuery(ptr) + case context.ContentFormMultipartHeaderValue: + err = ctx.ReadForm(ptr) + default: + err = ctx.ReadJSON(ptr) + // json + } + + // if err != nil { + // return emptyValue, err + // } + + if !wasPtr { + newValue = newValue.Elem() + } + + return newValue, err +} // Struct is being used to return a new injector based on // a struct value instance, if it contains fields that the types of those @@ -55,8 +85,6 @@ func Struct(s interface{}, values ...reflect.Value) *StructInjector { return MakeStructInjector( ValueOf(s), - DefaultHijacker, - DefaultTypeChecker, SortByNumMethods, Values(values).CloneWithFieldsOf(s)..., ) @@ -74,9 +102,6 @@ func Func(fn interface{}, values ...reflect.Value) *FuncInjector { return MakeFuncInjector( ValueOf(fn), - DefaultHijacker, - DefaultTypeChecker, - DefaultErrorHandler, values..., ) } @@ -88,28 +113,34 @@ func Func(fn interface{}, values ...reflect.Value) *FuncInjector { type D struct { Values - hijacker Hijacker - goodFunc TypeChecker - errorHandler ErrorHandler - sorter Sorter + fallbackBinder FallbackBinder + errorHandler ErrorHandler + sorter Sorter +} + +// OrphanInput represents an input without registered dependency. +// Used to help the framework (or the caller) auto-resolve it by the request. +type OrphanInput struct { + // Index int // function or struct field index. + Type reflect.Type } +// FallbackBinder represents a handler of oprhan input values, handler's input arguments or controller's fields. +type FallbackBinder func(ctx context.Context, input OrphanInput) (reflect.Value, error) + // New creates and returns a new Dependency Injection container. // See `Values` field and `Func` and `Struct` methods for more. func New() *D { - return &D{} -} - -// Hijack sets a hijacker function, read the `Hijacker` type for more explanation. -func (d *D) Hijack(fn Hijacker) *D { - d.hijacker = fn - return d + return &D{ + errorHandler: DefaultErrorHandler, + fallbackBinder: DefaultFallbackBinder, + } } -// GoodFunc sets a type checker for a valid function that can be binded, -// read the `TypeChecker` type for more explanation. -func (d *D) GoodFunc(fn TypeChecker) *D { - d.goodFunc = fn +// FallbackBinder adds a binder which will handle any oprhan input values. +// See `FallbackBinder` type. +func (d *D) FallbackBinder(fallbackBinder FallbackBinder) *D { + d.fallbackBinder = fallbackBinder return d } @@ -130,11 +161,10 @@ func (d *D) Sort(with Sorter) *D { // 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, - errorHandler: d.errorHandler, - sorter: d.sorter, + Values: d.Values.Clone(), + fallbackBinder: d.fallbackBinder, + errorHandler: d.errorHandler, + sorter: d.sorter, } } @@ -144,16 +174,19 @@ func (d *D) Clone() *D { // with the injector's `Inject` and `InjectElem` methods. func (d *D) Struct(s interface{}) *StructInjector { if s == nil { - return &StructInjector{Has: false} + return &StructInjector{} } - return MakeStructInjector( + injector := MakeStructInjector( ValueOf(s), - d.hijacker, - d.goodFunc, d.sorter, d.Values.CloneWithFieldsOf(s)..., ) + + injector.ErrorHandler = d.errorHandler + injector.FallbackBinder = d.fallbackBinder + + return injector } // Func is being used to return a new injector based on @@ -163,14 +196,16 @@ func (d *D) Struct(s interface{}) *StructInjector { // with the injector's `Inject` method. func (d *D) Func(fn interface{}) *FuncInjector { if fn == nil { - return &FuncInjector{Has: false} + return &FuncInjector{} } - return MakeFuncInjector( + injector := MakeFuncInjector( ValueOf(fn), - d.hijacker, - d.goodFunc, - d.errorHandler, d.Values..., - ).ErrorHandler(d.errorHandler) + ) + + injector.ErrorHandler = d.errorHandler + injector.FallbackBinder = d.fallbackBinder + + return injector } diff --git a/hero/di/func.go b/hero/di/func.go index 76c316107..9289f8c9e 100644 --- a/hero/di/func.go +++ b/hero/di/func.go @@ -18,10 +18,10 @@ type ( FuncInjector struct { // the original function, is being used // only the .Call, which is referring to the same function, always. - fn reflect.Value - typ reflect.Type - goodFunc TypeChecker - errorHandler ErrorHandler + fn reflect.Value + typ reflect.Type + FallbackBinder FallbackBinder + ErrorHandler ErrorHandler inputs []*targetFuncInput // Length is the number of the valid, final binded input arguments. @@ -51,13 +51,13 @@ func (s *FuncInjector) miss(index int, remaining Values) { // that the caller should use to bind input arguments of the "fn" function. // // The hijack and the goodFunc are optional, the "values" is the dependencies collection. -func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, errorHandler ErrorHandler, values ...reflect.Value) *FuncInjector { +func MakeFuncInjector(fn reflect.Value, values ...reflect.Value) *FuncInjector { typ := IndirectType(fn.Type()) s := &FuncInjector{ - fn: fn, - typ: typ, - goodFunc: goodFunc, - errorHandler: errorHandler, + fn: fn, + typ: typ, + FallbackBinder: DefaultFallbackBinder, + ErrorHandler: DefaultErrorHandler, } if !IsFunc(typ) { @@ -71,16 +71,12 @@ func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, e for i := 0; i < n; i++ { inTyp := typ.In(i) - if hijack != nil { - b, ok := hijack(inTyp) - - if ok && b != nil { - s.inputs = append(s.inputs, &targetFuncInput{ - InputIndex: i, - Object: b, - }) - continue - } + if b, ok := tryBindContext(inTyp); ok { + s.inputs = append(s.inputs, &targetFuncInput{ + InputIndex: i, + Object: b, + }) + continue } matched := false @@ -98,6 +94,50 @@ func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, e break } + + // TODO: (already working on it) clean up or even re-write the whole di, hero and some of the mvc, + // this is a dirty but working-solution for #1449. + // Limitations: + // - last input argument + // - not able to customize it other than DefaultFallbackBinder on MVC (on hero it can be customized) + // - the "di" package is now depends on context package which is not an import-cycle issue, it's not imported there. + if i == n-1 { + if v.Type() == autoBindingTyp && s.FallbackBinder != nil { + + canFallback := true + if k := inTyp.Kind(); k == reflect.Ptr { + if inTyp.Elem().Kind() != reflect.Struct { + canFallback = false + } + } else if k != reflect.Struct { + canFallback = false + } + + if canFallback { + matched = true + + s.inputs = append(s.inputs, &targetFuncInput{ + InputIndex: i, + Object: &BindObject{ + Type: inTyp, + BindType: Dynamic, + ReturnValue: func(ctx context.Context) reflect.Value { + value, err := s.FallbackBinder(ctx, OrphanInput{Type: inTyp}) + if err != nil { + if s.ErrorHandler != nil { + s.ErrorHandler.HandleError(ctx, err) + } + } + + return value + }, + }, + }) + + break + } + } + } } if !matched { @@ -108,7 +148,6 @@ func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, e // with different set of binding "values". s.miss(i, values) // send the remaining dependencies values. } - } return s @@ -119,6 +158,11 @@ func (s *FuncInjector) refresh() { s.Has = s.Length > 0 } +// AutoBindingValue a fake type to expliclty set the return value of hero.AutoBinding. +type AutoBindingValue struct{} + +var autoBindingTyp = reflect.TypeOf(AutoBindingValue{}) + func (s *FuncInjector) addValue(inputIndex int, value reflect.Value) bool { defer s.refresh() @@ -129,31 +173,14 @@ func (s *FuncInjector) addValue(inputIndex int, value reflect.Value) bool { inTyp := s.typ.In(inputIndex) // the binded values to the func's inputs. - b, err := MakeBindObject(value, s.goodFunc, s.errorHandler) + b, err := MakeBindObject(value, s.ErrorHandler) if err != nil { return false } - // TODO: expose that (need to push a fix for issue #1450 first) - if b.Type == reflectValueType { - b.Type = inTyp - // returnValue := b.ReturnValue - b.ReturnValue = func(ctx context.Context) reflect.Value { - newValue := reflect.New(inTyp) - - if err := ctx.ReadJSON(newValue.Interface()); err != nil { - if s.errorHandler != nil { - s.errorHandler.HandleError(ctx, err) - } - } - - return newValue.Elem() - } - } - if b.IsAssignable(inTyp) { - // fmt.Printf("binded input index: %d for type: %s and value: %v with pointer: %v\n", - // i, b.Type.String(), inTyp.String(), inTyp.Pointer()) + // fmt.Printf("binded input index: %d for type: %s and value: %v with dependency: %v\n", + // inputIndex, b.Type.String(), inTyp.String(), b) s.inputs = append(s.inputs, &targetFuncInput{ InputIndex: inputIndex, Object: &b, @@ -164,12 +191,6 @@ func (s *FuncInjector) addValue(inputIndex int, value reflect.Value) bool { return false } -// ErrorHandler registers an error handler for this FuncInjector. -func (s *FuncInjector) ErrorHandler(errorHandler ErrorHandler) *FuncInjector { - s.errorHandler = errorHandler - return s -} - // Retry used to add missing dependencies, i.e path parameter builtin bindings if not already exists // in the `hero.Handler`, once, only for that func injector. func (s *FuncInjector) Retry(retryFn func(inIndex int, inTyp reflect.Type, remainingValues Values) (reflect.Value, bool)) bool { diff --git a/hero/di/object.go b/hero/di/object.go index 96ed4dded..9759187db 100644 --- a/hero/di/object.go +++ b/hero/di/object.go @@ -45,10 +45,10 @@ type BindObject struct { // or the input arguments (if "v.elem()" is func) // are valid to be included as the final object's dependencies, even if the caller added more // the "di" is smart enough to select what each "v" needs and what not before serve time. -func MakeBindObject(v reflect.Value, goodFunc TypeChecker, errorHandler ErrorHandler) (b BindObject, err error) { +func MakeBindObject(v reflect.Value, errorHandler ErrorHandler) (b BindObject, err error) { if IsFunc(v) { b.BindType = Dynamic - b.ReturnValue, b.Type, err = MakeReturnValue(v, goodFunc, errorHandler) + b.ReturnValue, b.Type, err = MakeReturnValue(v, errorHandler) } else { b.BindType = Static b.Type = v.Type() @@ -58,6 +58,23 @@ func MakeBindObject(v reflect.Value, goodFunc TypeChecker, errorHandler ErrorHan return } +func tryBindContext(fieldOrFuncInput reflect.Type) (*BindObject, bool) { + if !IsContext(fieldOrFuncInput) { + return nil, false + } + // this is being used on both func injector and struct injector. + // if the func's input argument or the struct's field is a type of Context + // then we can do a fast binding using the ctxValue + // which is used as slice of reflect.Value, because of the final method's `Call`. + return &BindObject{ + Type: contextTyp, + BindType: Dynamic, + ReturnValue: func(ctx context.Context) reflect.Value { + return ctx.ReflectValue()[0] + }, + }, true +} + var errBad = errors.New("bad") // MakeReturnValue takes any function @@ -71,7 +88,7 @@ var errBad = errors.New("bad") // // The return type of the "fn" should be a value instance, not a pointer. // The binder function should return just one value. -func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker, errorHandler ErrorHandler) (func(ctx context.Context) reflect.Value, reflect.Type, error) { +func MakeReturnValue(fn reflect.Value, errorHandler ErrorHandler) (func(context.Context) reflect.Value, reflect.Type, error) { typ := IndirectType(fn.Type()) // invalid if not a func. @@ -86,10 +103,8 @@ func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker, errorHandler ErrorH return nil, typ, errBad } - if goodFunc != nil { - if !goodFunc(typ) { - return nil, typ, errBad - } + if !goodFunc(typ) { + return nil, typ, errBad } firstOutTyp := typ.Out(0) @@ -114,17 +129,6 @@ func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker, errorHandler ErrorH return firstZeroOutVal } - // if firstOutTyp == reflectValueType { - // converted := v.Convert(typ.In(0)) - // fmt.Printf("object.go#124: converted: %#+v\n", converted) - // return converted //reflect.ValueOf(v.Interface()) - // } - - // if v.String() == "" { - // println("di/object.go: " + v.String()) - // // println("di/object.go: because it's interface{} it should be returned as: " + v.Elem().Type().String() + " and its value: " + v.Elem().Interface().(string)) - // return v.Elem() - // } return v } diff --git a/hero/di/reflect.go b/hero/di/reflect.go index ccdc54977..2e3bbd5f8 100644 --- a/hero/di/reflect.go +++ b/hero/di/reflect.go @@ -2,6 +2,8 @@ package di import ( "reflect" + + "github.com/kataras/iris/v12/context" ) // EmptyIn is just an empty slice of reflect.Value. @@ -131,6 +133,19 @@ func goodVal(v reflect.Value) bool { return v.IsValid() } +var contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() + +// IsContext returns true if the "inTyp" is a type of Context. +func IsContext(inTyp reflect.Type) bool { + return inTyp.Implements(contextTyp) +} + +func goodFunc(fn reflect.Type) bool { + // valid if that single input arg is a typeof context.Context + // or first argument is context.Context and second argument is a variadic, which is ignored (i.e new sessions#Start). + return (fn.NumIn() == 1 || (fn.NumIn() == 2 && fn.IsVariadic())) && IsContext(fn.In(0)) +} + // IsFunc returns true if the passed type is function. func IsFunc(kindable interface { Kind() reflect.Kind diff --git a/hero/di/struct.go b/hero/di/struct.go index 8a165db06..ebcf77c3a 100644 --- a/hero/di/struct.go +++ b/hero/di/struct.go @@ -61,6 +61,9 @@ type ( Has bool CanInject bool // if any bindable fields when the state is NOT singleton. Scope Scope + + FallbackBinder FallbackBinder + ErrorHandler ErrorHandler } ) @@ -103,11 +106,13 @@ var SortByNumMethods Sorter = func(t1 reflect.Type, t2 reflect.Type) bool { // 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, sorter Sorter, values ...reflect.Value) *StructInjector { +func MakeStructInjector(v reflect.Value, sorter Sorter, values ...reflect.Value) *StructInjector { s := &StructInjector{ initRef: v, initRefAsSlice: []reflect.Value{v}, elemType: IndirectType(v.Type()), + FallbackBinder: DefaultFallbackBinder, + ErrorHandler: DefaultErrorHandler, } // Optionally check and keep good values only here, @@ -138,15 +143,12 @@ func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker, 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{ - FieldIndex: f.Index, - Object: b, - }) - - continue - } + if b, ok := tryBindContext(f.Type); ok { + s.fields = append(s.fields, &targetStructField{ + FieldIndex: f.Index, + Object: b, + }) + continue } var possibleValues []*targetStructField @@ -157,9 +159,10 @@ func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker, } // the binded values to the struct's fields. - b, err := MakeBindObject(val, goodFunc, nil) + b, err := MakeBindObject(val, nil) if err != nil { - return s // if error stop here. + panic(err) + // return s // if error stop here. } if b.IsAssignable(f.Type) { diff --git a/hero/handler.go b/hero/handler.go index cb646350b..363793697 100644 --- a/hero/handler.go +++ b/hero/handler.go @@ -11,31 +11,17 @@ import ( "github.com/kataras/golog" ) -var contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() +// var genericFuncTyp = reflect.TypeOf(func(context.Context) reflect.Value { return reflect.Value{} }) -// IsContext returns true if the "inTyp" is a type of Context. -func IsContext(inTyp reflect.Type) bool { - return inTyp.Implements(contextTyp) -} - -// var genericFuncTyp = reflect.TypeOf(func(context.Context) interface{} { return nil }) -var genericFuncTyp = reflect.TypeOf(func(context.Context) reflect.Value { return reflect.Value{} }) - -// IsGenericFunc reports whether the "inTyp" is a type of func(Context) interface{}. -func IsGenericFunc(inTyp reflect.Type) bool { - return inTyp == genericFuncTyp -} +// // IsGenericFunc reports whether the "inTyp" is a type of func(Context) interface{}. +// func IsGenericFunc(inTyp reflect.Type) bool { +// return inTyp == genericFuncTyp +// } // checks if "handler" is context.Handler: func(context.Context). func isContextHandler(handler interface{}) (context.Handler, bool) { - h, is := handler.(context.Handler) - if !is { - fh, is := handler.(func(context.Context)) - if is { - return fh, is - } - } - return h, is + h, ok := handler.(context.Handler) + return h, ok } func validateHandler(handler interface{}) error { @@ -72,7 +58,7 @@ func makeHandler(handler interface{}, errorHandler di.ErrorHandler, values ...re } funcInjector := di.Func(fn, values...) - funcInjector.ErrorHandler(errorHandler) + funcInjector.ErrorHandler = errorHandler valid := funcInjector.Length == n diff --git a/hero/handler_test.go b/hero/handler_test.go index 3ee73a6bb..fbd286dc4 100644 --- a/hero/handler_test.go +++ b/hero/handler_test.go @@ -4,7 +4,6 @@ package hero_test import ( "fmt" - "reflect" "testing" "github.com/kataras/iris/v12" @@ -128,37 +127,23 @@ func TestBindFunctionAsFunctionInputArgument(t *testing.T) { Expect().Status(iris.StatusOK).Body().Equal(expectedUsername) } -func TestBindReflectValue(t *testing.T) { - // TODO: THINK of simplify this, - // as 'hero' and 'mvc' are not depend on the root kataras/iris/v12 package, smart decision back then. - // e.g. - // app := iris.New() - // app.RegisterDependency(...) - // app.HandleFunc("GET POST", "/", func(input MyInput) MyOutput {}) - // instead of: - // app := iris.New() - // h := hero.New() - // h.Register(...) or hero.Register for shared deps across Iris different applications. - // handler := h.Handler(func(input MyInput) MyOutput {}) - // app.HandleMany("GET POST", "/", handler) - +func TestAutoBinding(t *testing.T) { h := New() - h.Register(func(ctx iris.Context) reflect.Value { - var v interface{} - err := ctx.ReadJSON(&v) - if err != nil { - t.Fatal(err) - } - return reflect.ValueOf(v) - // return reflect.Value{} + h.Register(AutoBinding) + + postHandler := h.Handler(func(input *testUserStruct /* ptr */) string { + return input.Username }) - postHandler := h.Handler(func(input testUserStruct) string { + + postHandler2 := h.Handler(func(input testUserStruct) string { return input.Username }) app := iris.New() app.Post("/", postHandler) + app.Post("/2", postHandler2) e := httptest.New(t, app) e.POST("/").WithJSON(iris.Map{"username": "makis"}).Expect().Status(httptest.StatusOK).Body().Equal("makis") + e.POST("/2").WithJSON(iris.Map{"username": "kataras"}).Expect().Status(httptest.StatusOK).Body().Equal("kataras") } diff --git a/hero/hero.go b/hero/hero.go index 0570be28d..cd1117b6e 100644 --- a/hero/hero.go +++ b/hero/hero.go @@ -48,6 +48,12 @@ func (h *Hero) Dependencies() *di.Values { return &h.values } +// AutoBinding used to be registered as dependency to try to automatically +// map and bind the inputs that are not already binded with a dependency. +// +// See `DefaultFallbackBinder`. +var AutoBinding = di.AutoBindingValue{} + // Register adds one or more values as dependencies. // The value can be a single struct value-instance or a function // which has one input and one output, the input should be diff --git a/iris.go b/iris.go index 268d4be6e..1b3bbf015 100644 --- a/iris.go +++ b/iris.go @@ -41,7 +41,7 @@ import ( ) // Version is the current version number of the Iris Web Framework. -const Version = "12.1.7" +const Version = "12.1.8" // HTTP status codes as registered with IANA. // See: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml. diff --git a/mvc/controller.go b/mvc/controller.go index 63f43404c..0137f3140 100644 --- a/mvc/controller.go +++ b/mvc/controller.go @@ -396,8 +396,6 @@ func (c *ControllerActivator) attachInjector() { if c.injector == nil { c.injector = di.MakeStructInjector( di.ValueOf(c.Value), - di.DefaultHijacker, - di.DefaultTypeChecker, c.sorter, di.Values(c.dependencies).CloneWithFieldsOf(c.Value)..., ) @@ -426,7 +424,7 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref // fmt.Printf("for %s | values: %s\n", funcName, funcDependencies) funcInjector := di.Func(m.Func, funcDependencies...) - funcInjector.ErrorHandler(c.errorHandler) + funcInjector.ErrorHandler = c.errorHandler // fmt.Printf("actual injector's inputs length: %d\n", funcInjector.Length) if funcInjector.Has { @@ -492,10 +490,6 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref return // stop as soon as possible, although it would stop later on if `ctx.StopExecution` called. } - // for idxx, inn := range in { - // println("controller.go: execution: in.Value = "+inn.String()+" and in.Type = "+inn.Type().Kind().String()+" of index: ", idxx) - // } - hero.DispatchFuncResult(ctx, errorHandler, call(in)) return } diff --git a/mvc/mvc.go b/mvc/mvc.go index fd1121ba5..cf2bf92eb 100644 --- a/mvc/mvc.go +++ b/mvc/mvc.go @@ -104,6 +104,12 @@ func (app *Application) Configure(configurators ...func(*Application)) *Applicat return app } +// AutoBinding used to be registered as dependency to try to automatically +// map and bind the inputs that are not already binded with a dependency. +// +// A shortcut of `hero.AutoBinding`. Read more at: `hero#DefaultFallbackBinder`. +var AutoBinding = hero.AutoBinding + // Register appends one or more values as dependencies. // The value can be a single struct value-instance or a function // which has one input and one output, the input should be